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.

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.

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

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!

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 78

Comments are closed.
  1. I`m very thnx u fo this amazing thing!

    I want to help a little.
    In sourse archive you must change hover.js code in line #21. Now you have

    to : el.getAttribute( ‘data-path-to’ )

    that must be

    to : el.getAttribute( ‘data-path-hover’ )

  2. Would love to use this hover effect on a project I’m working on. Unfortunately it seems to still not work correctly on Safari. The top white section does not animate 🙁 It seems allot of changes suggested in the comments have been implemented into the demo pages, however safari still does not work in the same way as chrome and firefox. Anyone found a way for this to work on all browsers?

  3. How can i add the link to the button?
    If i create <a href=”www.google.cl” rel=”nofollow”>Google</a> or <a href=”www.google.cl” rel=”nofollow”>Google</a>it destroy all the css because the container is an a :'(

  4. hi, i love this.. I’m new and i can´t correct the grid top margins of each element, i want to have them in the same row, hope it understands. Thanks

  5. For those asking why you can’t change the <button><!--formatted--> to an <a href=""><!--formatted-->, it’s likely because the whole thing is already encapsulated in a <a href=""><!--formatted--> so you end up with a link inside another link, and that doesn’t work. Not to mention, is not valid.

    I have to admit that using a <button><!--formatted--> in there isn’t entirely semantic and it looks more like a CTA (call to action) rather than an actual button that will process data on the server.

    So all we need to do is change the <button><!--formatted--> to a <span><!--formatted--> and add a class of .button: <span class="button"><!--formatted-->. In the CSS all instances of the button selector need to be updated to .button of course.

    Here’s a streamlined version of demo 1 regarding the :hover pseudo-class: http://codepen.io/ricardozea/pen/a0f484c0a175b0a2d8ed9a7a256d0078

    • hey ricardo…is there anyway I can add any other resolution other than 250*437? for image behind that animation?..please help me out !! urgent!!

  6. hey awesome stuff..but does it work only on images which has 250*437? I tried image with similar resolution 250*325…and it turned out to be bug..please help me out….

  7. thanks for sharing it!

    I would like to embed something like this is a wordpress webpage. Could you please tell me how i could do it?

    Thank you a lot.