Shape Hover Effect with SVG

In this tutorial we'll recreate the hover effect as seen on The Christmas Experiments website. We'll be using SVG for the shape and Snap.svg for animating it on hover.

ShapeHoverEffect

View demo Download source

If you have visited the fantastic new edition of The Christmas Experiments then you might have noticed the really cool hover effect in the Christmas calendar that uses a triangular shape. The shape is made up of a triangle using borders and today I would like to show you how to achieve the same effect using SVG and Snap.svg. The idea is to create a SVG with one path that represents the shape background for some caption and to morph that path into another one on hover. There are many creative possibilities and today we’ll create three different examples. The nice thing of utilizing SVG is that we can adjust the shape size to their parent container’s size and make everything fluid.

The illustrations used in the demos are by talented Isaac Montemayor. See his original artwork on his website or Dribbble.

So, let’s get started!

The Markup

What we’ll do first, is to draw two shapes in a vector graphics editor like Adobe Illustrator or Inkscape. Each shape will consist of one path and when we are done, we’ll copy the points of our paths to be used in our markup. Note that we’ve transformed a polygon into a path. If you are using Inkscape you can do that by selecting the object and choosing Path > Object to Path. The points for the path can be obtained from Edit > XML Editor… which will open a view as seen in the following screenshot:

InkscapeShape

The “d” (path data) value is what you are looking for.

For the markup we’ll have a section with the class “grid” which contains figures wrapped in anchors. You could as well use a list here which would need some extra markup.
The figure will contain the image, the initially visible shape and a figcaption:

<section id="grid" class="grid clearfix">
	<a href="#" data-path-hover="m 180,34.57627 -180,0 L 0,0 180,0 z">
		<figure>
			<img src="img/1.png" />
			<svg viewBox="0 0 180 320" preserveAspectRatio="none"><path d="M 180,160 0,218 0,0 180,0 z"/></svg>
			<figcaption>
				<h2>Crystalline</h2>
				<p>Soko radicchio bunya nuts gram dulse.</p>
				<button>View</button>
			</figcaption>
		</figure>
	</a>
	<!-- ... -->
</section>

The SVG will have the respective viewBox values for the graphic and a preserveAspectRatio of “none”. This will allow us to resize and stretch the shape to the dimensions we want, which is 100% in this case. We’ll define the width and height in our style sheet. The information of the hover path will be stored in the data-attribute data-path-hover of the wrapping anchor.

Let’s style all this.

The CSS

Note that the CSS will not contain any vendor prefixes, but you will find them in the files.
The style that we’ll be going through is for all three examples. First, we’ll take a look at the common style and then we’ll set the individual styles for all the demos.

We’ll start with the grid. Let’s center it and give it a max-width and a percentage width to make it fluid:

.grid {
	margin: 40px auto 120px;
	max-width: 1000px;
	width: 90%;
}

The anchors should float left and we’ll give them a max-width of 250px and a width of 25% since we’d like them to be fluid and show four items in a row. We’ll take care of smaller screen sizes in our media queries later on:

.grid a {
	float: left;
	max-width: 250px;
	width: 25%;
	color: #333;
}

To create some offset for the odd items, we’ll set a top margin of 30px and a bottom margin of -30px. This will create a nice look for the grid, just like in the grid on The Christmas Experiments website:

.grid a:nth-child(odd) {
	margin: 30px 0 -30px 0;
}

The figure should be positioned relatively because we’ll need some of the children to be absolute. Since our hover effect might cause some overflow, and we don’t want that to be visible, we’ll set the overflow to “hidden”:

.grid figure {
	position: relative;
	overflow: hidden;
	margin: 5px;
	background: #333;
}

The image will occupy all the width of its parent and the opacity is set to 0.7. On hover we want to animate the opacity, so we’ll add a transition:

.grid figure img {
	position: relative;
	display: block;
	width: 100%;
	opacity: 0.7;
	transition: opacity 0.3s;
}

The figcaption needs to be absolutely positioned and we’ll stretch it over the whole item:

.grid figcaption {
	position: absolute;
	top: 0;
	z-index: 11;
	padding: 10px;
	width: 100%;
	height: 100%;
	text-align: center;
}

The headline and the paragraph will both be animated on hover, so let’s give them the respective transitions and move their initial position a bit:

.grid figcaption h2 {
	margin: 0 0 20px 0;
	color: #3498db;
	text-transform: uppercase;
	letter-spacing: 1px;
	font-weight: 300;
	font-size: 130%;
	transition: transform 0.3s;
}

.grid figcaption p {
	padding: 0 20px;
	color: #aaa;
	font-weight: 300;
	transition: opacity 0.3s, transform 0.3s;
}

.grid figcaption h2,
.grid figcaption p {
	transform: translateY(50px);
}

The common button style for all three examples is the following. The button will also animate, so we’ll add a transition for the opacity and for the transform:

.grid figure button {
	position: absolute;
	padding: 4px 20px;
	border: none;
	text-transform: uppercase;
	letter-spacing: 1px;
	font-weight: bold;
	transition: opacity 0.3s, transform 0.3s;
}

To avoid some flickering and glitches, we’ll need to give the animating elements and their parent a hidden backface-visibility:

.grid figcaption,
.grid figcaption h2,
.grid figcaption p,
.grid figure button {
	backface-visibility: hidden;
}

The SVG will also be positioned absolutely and we’ll stretch it over the item by setting the width and height to 100%. Giving it a top value of -1px instead of 0 will ensure that there is not strange line in Firefox (26.0 / Mac).

.grid svg {
	position: absolute;
	top: -1px; /* fixes rendering issue in FF */
	z-index: 10;
	width: 100%;
	height: 100%;
}

The fill color of the path will be white:

.grid svg path {
	fill: #fff;
}

At this point you could also try to add some transitions to the path and e.g. change the fill color on hover.

The common hover effects for the anchors will be the following:

.grid a:hover figure img {
	opacity: 1;
}

.grid a:hover figcaption h2,
.grid a:hover figcaption p {
	transform: translateY(0);
}

.grid a:hover figcaption p {
	opacity: 0;
}

The opacity of the image will be set to 1 while the paragraph of the captions slides up and disappears. The headline will move to up, too.

Let’s style the individual demos. Some of the styles will be common to more than one demo.

In the first and third example, we want the button to have a white border and be centered on the item. It will be hidden initially and scaled down. The other transforms are used to “pull” it into position and center it. On hover, we’ll make the button scale up and fade in:

.demo-1 body {
	background: #3498db;
}

.demo-1 .grid figure button,
.demo-3 .grid figure button {
	top: 50%;
	left: 50%;
	border: 3px solid #fff;
	background: transparent;
	color: #fff;
	opacity: 0;
	transform: translateY(-50%) translateX(-50%) scale(0.25);
}

.demo-1 .grid a:hover figure button,
.demo-3 .grid a:hover figure button {
	opacity: 1;
	transform: translateY(-50%) translateX(-50%) scale(1);
}

For the second demo, we’ll redefine some colors and set the button to be hidden at the bottom of the figure. For that we set the bottom to 0 and translate it 100% (of its own height). On hover we’ll make it slide up with a different easing function “ease-out” (the deafult is “ease”):

.demo-2 body {
	background: #e74c3c;
}

.demo-2 .grid figcaption h2 {
	color: #e74c3c;
}

.demo-2 .grid figcaption p {
	transition-delay: 0.05s;
}

.demo-2 .grid figure button {
	bottom: 0;
	left: 0;
	padding: 15px;
	width: 100%;
	background: #fff;
	color: #333;
	font-weight: 300;
	transform: translateY(100%);
}

.demo-2 .grid a:hover figure button {
	transition-timing-function: ease-out;
	transform: translateY(0);
}

The headline and paragraph of the second and third example will have a cubic-bezier easing function that will help emulate an elastic transition. We’ll also set a different duration with no delay for the paragraph when hovering. This will ensure that the paragraph fades out quickly before our SVG shape reaches the top:

.demo-2 .grid figcaption h2, 
.demo-2 .grid figcaption p,
.demo-3 .grid figcaption h2,
.demo-3 .grid figcaption p {
	timing-function: cubic-bezier(0.250, 0.250, 0.115, 1.445);
}

.demo-2 .grid a:hover figcaption p,
.demo-3 .grid a:hover figcaption p {
	transition-delay: 0s;
	transition-duration: 0.1s;
}

For the third demo we’ll change some colors and leave the headline translated a bit on hover instead of making it go to 0:

.demo-3 body {
	background: #52be7f;
}

.demo-3 .grid figcaption h2 {
	color: #52be7f;
}

.demo-3 .grid a:hover figcaption h2 {
	transform: translateY(5px);
}

For smaller screens, we want to change the number of items in a row, so we’ll reset the width and also the margin of the odd children. The anchors that should have a margin are the 2nd, 5th, 8th, 11th and so on, which is described by the sequence 3n-1. Tip: If you want to find the sequence to a row of numbers quickly, you can for example go to WolframAlpha and type your numbers. A possible sequence identification with the closed form can be found in one last boxes.

For even smaller sizes, we’ll adjust the max-width of the grid and redefine some margins for the caption elements:

@media screen and (max-width: 58em) {
	.grid a {
		width: 33.333%;
	}

	.grid a:nth-child(odd) {
		margin: 0;
	}

	.grid a:nth-child(3n-1) {
		margin: 30px 0 -30px 0;
	}
}

@media screen and (max-width: 45em) {
	.grid {
		max-width: 500px;
	}

	.grid a {
		width: 50%;
	}

	.grid a:nth-child(3n-1) {
		margin: 0;
	}

	.grid a:nth-child(even) {
		margin: 30px 0 -30px 0;
	}


	.grid figcaption h2 {
		margin-bottom: 0px;
		transform: translateY(30px);
	}

	.grid figcaption p {
		margin: 0;
		padding: 0 10px;
	}
}

@media screen and (max-width: 27em) {
	.grid {
		max-width: 250px;
	}

	.grid a {
		width: 100%;
	}

	.grid a:nth-child(even) {
		margin: 0;
	}
}

And that’s all the style! Let’s move over to the JavaScript.

The JavaScript

We’ll be using Snap.svg, a great library for working with SVGs. Please take a look at the documentation and the interactive Getting Started tutorial for a better understanding on how it can be used.

Let’s start by defining the speed and the easing variable. We’ll also define a variable that will contain the info on the path and the hover path. When hovering over an anchor, we’ll animate the path to morph into the “to” path. When leaving the anchor, we’ll animate back to the “from” path:

(function() {
	
	function init() {
		var speed = 250,
			easing = mina.easeinout;

		[].slice.call ( document.querySelectorAll( '#grid > a' ) ).forEach( function( el ) {
			var s = Snap( el.querySelector( 'svg' ) ), path = s.select( 'path' ),
				pathConfig = {
					from : path.attr( 'd' ),
					to : el.getAttribute( 'data-path-hover' )
				};

			el.addEventListener( 'mouseenter', function() {
				path.animate( { 'path' : pathConfig.to }, speed, easing );
			} );

			el.addEventListener( 'mouseleave', function() {
				path.animate( { 'path' : pathConfig.from }, speed, easing );
			} );
		} );
	}

	init();

})();

And that’s it! We’ve just shown you three examples, but there are so many possibilities for cool shape morphing or color effects.

I hope you enjoyed this tutorial and find it inspiring!

View demo Download source

Previous:
Next:

Tagged with:

Mary Lou (Manoela Ilic) is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by

Website: http://tympanus.net/

Related Articles

Feedback 56

  1. 1

    Nice Work ,
    I have a problem with the add link the “button”

    sample :

    <a href=”http://tympanus.net” rel=”nofollow”>view</a>
    or
    <a href=”http://tympanus.net” rel=”nofollow”>view</a>

    How you need to do “button” link

    • 2

      <button>Web Site</button>
      <a href="http://tympanus.net" ><button>Web Site</button></a>
      or
      <button><a href="http://tympanus.net" >Web Site</a></button>

  2. 3

    I’ve tried to adapt this and use it on my own site… when i transfer all the CSS and js it doesn’t work at all. Has anyone else had bother with this and if so, how did you fix it

  3. 5

    Hi Mary Lou, thanks for the great tutorial! I have it installed on my site (kzuagency.com/projects) and everything is working great except that when rolling over, I get a 1px image shift, not sure what thats about. I’m using firefox 30.0, and the demo on your site is doing the same thing. So it looks like something to do with firefox. I found this article and somehow it seems related:

    http://stackoverflow.com/questions/12830897/svg-renders-but-gets-cut-off-in-firefox-only-why

    do you know a fix for this? i tried this solution, somethings happening but i’m not sure what… still getting the 1px image shift. It works great on Chrome. On Safari, when rolling over the thumbnails the main menu text goes from regular to bold text. Any help with this would be greatly appreciated :) thanks

  4. 6

    Why is the JS necessary? You can do it with SVG or even a background image and target it with the AFTER ELEMENT and animate it with some transitions? Someone explain this to me please.

Follow this discussion

Leave a Comment

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>