Hover and Click Trigger for Circular Elements with jQuery

Today we want to share one possible solution to the circle hovering problem. We’ll create a plugin that will take care of the ‘mouseenter’, ‘mouseleave’ and ‘click’ events to be triggered only on the circular shape of the element and not its bounding box.

HoverClickTriggerCircles

Applying a :hover pseudo-class to an element is widely known as the classic “hovering” over an element on a web page. A problem that arose with the introduction of the border-radius property is the non-realistic triggering of the hover event when entering the bounding box of the element and not just the actual visible area. This becomes extreme when we create a circle by setting the border-radius of a square to 50% (half of its outer width and height).

Today we want to share one possible solution to the circle hovering problem. We’ll create a plugin that will take care of the ‘mouseenter’, ‘mouseleave’ and ‘click’ events to be triggered only on the circular shape of the element and not its bounding box.

The image used in the demo is by Andrey & Lili. They are licensed under the Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0) License.

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

How it works

In our example, we’ll be creating a circle with some kind of hover effect. The structure will simply be:

<a href="#" id="circle" class="ec-circle">
	<h3>Hovered</h3>
</a>

And the style will be the following:

.ec-circle{
	width: 420px;
	height: 420px;
	-webkit-border-radius: 210px;
	-moz-border-radius: 210px;
	border-radius: 50%;
	text-align: center;
	overflow: hidden;
	font-family:'Kelly Slab', Georgia, serif;
	background: #dda994 url(../images/1.jpg) no-repeat center center;
	box-shadow: 
		inset 0 0 1px 230px rgba(0,0,0,0.6),
		inset 0 0 0 7px #d5ad94;
	transition: box-shadow 400ms ease-in-out;
	display: block;
	outline: none;
}

Now, we will define a class for the hover effect but not a dynamic pseudo-class :hover. The idea is to apply this class then with jQuery when we enter the circular area of our element:

.ec-circle-hover{
    box-shadow: 
		inset 0 0 0 0 rgba(0,0,0,0.6),
		inset 0 0 0 20px #c18167,
		0 0 10px rgba(0,0,0,0.3);
}

Only when we have JavaScript disabled, we’ll add the pseudo-class. This style can be found in the noscript.css:

.ec-circle:hover{
    box-shadow: 
		inset 0 0 0 0 rgba(0,0,0,0.6),
		inset 0 0 0 20px #c18167,
		0 0 10px rgba(0,0,0,0.3);
}

The JavaScript

We are going to create a simple plugin that basically “redefines” the three events mentioned earlier. We’ll make the events only applicable on the circular shape:

$.CircleEventManager 			= function( options, element ) {
	
	this.$el			= $( element );
	
	this._init( options );
	
};

$.CircleEventManager.defaults 	= {
	onMouseEnter	: function() { return false },
	onMouseLeave	: function() { return false },
	onClick			: function() { return false }
};

$.CircleEventManager.prototype 	= {
	_init 				: function( options ) {
		
		this.options 		= $.extend( true, {}, $.CircleEventManager.defaults, options );
		
		// set the default cursor on the element
		this.$el.css( 'cursor', 'default' );
		
		this._initEvents();
		
	},
	_initEvents			: function() {
		
		var _self	= this;
		
		this.$el.on({
			'mouseenter.circlemouse'	: function( event ) {
				
				var el	= $(event.target),
				
						  circleWidth	= el.outerWidth( true ),
						  circleHeight	= el.outerHeight( true ),
						  circleLeft	= el.offset().left,
						  circleTop		= el.offset().top,
						  circlePos		= {
							  x		: circleLeft + circleWidth / 2,
							  y		: circleTop + circleHeight / 2,
							  radius: circleWidth / 2
						  };
				
				// save cursor type
				var cursor	= 'default';
				
				if( _self.$el.css('cursor') === 'pointer' || _self.$el.is('a') )
					cursor = 'pointer';
					
				el.data( 'cursor', cursor );
				
				el.on( 'mousemove.circlemouse', function( event ) {

					var distance	= Math.sqrt( Math.pow( event.pageX - circlePos.x, 2 ) + Math.pow( event.pageY - circlePos.y, 2 ) );
					
					if( !Modernizr.borderradius ) {
						
						// inside element / circle
						el.css( 'cursor', el.data('cursor') ).data( 'inside', true );
						_self.options.onMouseEnter( _self.$el );
					
					}
					else {
					
						if( distance <= circlePos.radius && !el.data('inside') ) {
							
							// inside element / circle
							el.css( 'cursor', el.data('cursor') ).data( 'inside', true );
							_self.options.onMouseEnter( _self.$el );
							
						}
						else if( distance > circlePos.radius && el.data('inside') ) {
							
							// inside element / outside circle
							el.css( 'cursor', 'default' ).data( 'inside', false );
							_self.options.onMouseLeave( _self.$el );
						
						}
					
					}
				
				});	
				
			},
			'mouseleave.circlemouse'	: function( event ) {
				
				var el 	= $(event.target);
	
				el.off('mousemove');
				
				if( el.data( 'inside' ) ) {
				
					el.data( 'inside', false );
					_self.options.onMouseLeave( _self.$el );
				
				}
				
			},
			'click.circlemouse'			: function( event ) {
				
				// allow the click only when inside the circle
				
				var el 	= $(event.target);
				
				if( !el.data( 'inside' ) )
					return false;
				else
					_self.options.onClick( _self.$el );
				
			}
		});
		
	},
	destroy				: function() {
	
		this.$el.unbind('.circlemouse').removeData('inside').removeData('cursor');

	}
};

When we enter with the mouse in the square bounding box of our circle, we bind the ‘mousemove’ event to the element and like that we can track if the distance of the mouse to the center of the element if longer than the radius. If it is, we know that we are not yet hovering the circular area of the element.

HoverTrigger
Once the distance of the mouse is shorter than the radius, we know that we entered the circle and we trigger our custom ‘mouseenter’ event.

We also only allow the click event when the mouse is inside of the circle.

In our example we will then apply our plugin to the regarding element. In our case, we are adding the hover class on ‘mouseenter’ and removing it on ‘mouseleave’.

$('#circle').circlemouse({
	onMouseEnter	: function( el ) {
	
		el.addClass('ec-circle-hover');
	
	},
	onMouseLeave	: function( el ) {
		
		el.removeClass('ec-circle-hover');
		
	},
	onClick			: function( el ) {
		
		alert('clicked');
		
	}
});

Remember that the “normal” pseudo hover class is also defined in the noscript.css which gets applied when JavaScript is disabled.

I hope you find this useful!

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

πŸ‘Ύ Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 33

Comments are closed.
  1. Mary,

    Brilliant in its simplicity. Thinking of correlating the distance between the mouse pointer and the center to the radius of the circle was smart. A little tip : in your destroy function, the removeData() function can actually take space separated terms. I.e. you can write removeData(‘inside cursor’)

  2. GREAT Mary Lou !!!

    by the way this effect is not supported by opera ?

    Salute,
    Dicky Dwijayanto

  3. This is very nice! I tried to clone the circle caption to use the same effect on two circles – but in the secon one it doesnt work. Where did i failed? What ist the reason?
    Thank You!
    B.

    • @Bunk you actually don’t need to add an id per circle. If your circles are inside a div, for instance:

      <div id=”circles”>
      <a class=”ec-circle”></a> // this is a circle
      <a class=”ec-circle”></a> // this is a circle
      <a class=”ec-circle”></a> // this is a circle
      <a class=”ec-circle”></a> // this is a circle
      </div>

      you can call the plugin like this:

      $(‘#circles > a.ec-circle’).circlemouse({
      onMouseEnter : function( el ) {
      el.addClass(‘ec-circle-hover’);
      },
      onMouseLeave : function( el ) {
      el.removeClass(‘ec-circle-hover’);
      }
      });

      Hope it helps!
      Cheers, ML

  4. Mary Lou, your Idea is much simpler, but iΒ΄m going to ad different colors and pictures…
    Thnks for comment
    B.

  5. Under Internet Explorer, the text doesn’t appear …
    Does someone know why it doesn’t work undernet this browser ?

  6. How about performance?

    In my upcoming portfolio all items are displayed in a circle for each one. The hover triggers an animation that is actual very fluid even if i travel random over the screen with my cursor.

    This script would suit perfect for me if it comes without any delays or sluggish reactions with so many circles triggered in a short time..

  7. That’s wonderfull. It’s a pitty that don’t work in IE8 and IE 7. Unfortunnely I still have to make compatibility with this two. πŸ™ Does anyone has any suggestion in how to make a very similar effect like this compatible with ie7 & 8?? Thanks

  8. @PEDROR

    When designing for an obsolete browser, you’re going to have to make compromises. Create a base design for your IE audience, then enhance the experience with things like what is demonstrated in this demo. Modernizr helps with this.

    The only problem I see with this demo – “Select All”

  9. Wonderful plugin! If only it could work in more ‘scenario’ . It doesn’t work when the element is in position:absolute, or when you animate the size of the element =( Still, amazing job!

  10. Stupid question but why are specifying 210px for the vender prefixes and 50% with the standard ? does it matter?

  11. Wow! I’ve been looking far & wide for a solution to the hover state to fire only over the circle shape. However, I think I was a bit too excited because I can’t get it to work fully on my site.

    Problem: I am using overlapping circles and need the hover state to work on each of them. Currently I am using an image map to handle this but I’d like to use css3/jquery or another updated coding means to handle this to have a bit more control with fade-in/out effects etc. I’m not the best developer but can generally understand what is going on with the code.

    My current site with image map: http://www.stevenheld.com
    testing site with your code: http://www.stevenheld.com/HoverClickTriggerCircle/index.html

    Using Safari/Chrome browsers the hover element on the inner 2 circles on my site still fire over the box not within the border radius like the outer circle did. I’m thinking it has something to do with having to rename the classes and id’s but I’m not sure and wondered if you would be able to provide your expertise?

    Thanks,
    Steven Held

  12. For some reason I couldn’t get this to work at all in WordPress… or I could, just in different stages. But never 100%?

  13. Thank You Mary for all the plugin examples, this has been a big help for me learning jQuery and css.
    You are awesome!!!!

  14. Thanks Mary Lou, your tutorials always amaze me, I think u should have your own website and/or book!! your explanations are always so good and I’ve learned a lot from you! πŸ™‚