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

Fresh news, inspo, code demos, and UI animationsβ€”zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 78

Comments are closed.
  1. I LOVE these!! awesome work, Manoela!! =))

    P.S: the download demo button is giving a 404..

  2. Thanks a lot everybody!! Sorry about the download link, it should be working fine now! Cheers, ML

  3. How do you come up with these great pieces?
    Way over my head. πŸ™‚

    Excellent work! Always an inspiration.

  4. Nice effects, but I don’t really understand why you use SVG instead of simple CSS. Especially for demos 1 & 3 (#2 can be a pain to recreate with pure CSS, I agree).
    If I have some time, I’ll try to recreate it with pure CSS (and avoid JS too), just to be sure… πŸ˜‰

  5. Really good tutorial. Thx a lot.
    Can you also provide the shape1.svg please?
    It helps to follow your description in case of data-path-hover-values and path-value.
    Is the viewbox-value the given size of the box or do i get it from the svg-file?

    • Here are the two shapes of the first example: Shape 1, Shape 2

      The other shapes can be “constructed” in the same way, simply copy the path into the SVG. The viewBox value is the size of the SVG.

      Let me know if this helps and thanks for your feedback!

  6. Awesome! Really cool. I’d love to use techniques like this in my day-to-day dev. What’s the browser support here? How does this degrade? Thanks!

  7. Hey , thanks for the tutorial , i am just wondering how to get the same values in Illustrator , which is the “d” here (path data) !! or do i need optimizer !!

  8. Hi,
    Firefox giving a black line of one Pixel on the Right Side of Image
    would you help in This Please ?
    Thanks…

    • Hey, Yousuff, what did you do? I’m noticing the same problem as well and haven’t pinned down the source of the issue yet.

    • Plus one on this. Anyone know how to fix for safari? I am going to look into it…

    • Safari browser doesn’t have “mouseenter” and “mouseleave” events, it has “mouseover” “mouseout” instead.
      Just add this to init function
      el.addEventListener( 'mouseover', function() { path.animate( { 'path' : pathConfig.to }, speed, easing ); } ); el.addEventListener( 'mouseout', function() { path.animate( { 'path' : pathConfig.from }, speed, easing ); } );

    • Safari supports mouseenter and mouseleave … Another problem could be that some versions of safari need the svg contents to be in a tag. It’s a bug I believe.

  9. I have a problem with the “button”.
    Takes a strange style and shows all the time, not just on hover.
    The code is perfect, help me!
    It’s happened with someone else?

  10. Hello,

    Does someone know how to make this work in wordpress?
    Where can i find a tutorial on how to put js code in wordpress?

    Thanks,

    Will

  11. Hey, nice work!

    I have a question for you. cause i’m new to svg. could u please tell me how you get the value for ‘data-path-hover’? and also the d attribute for

    tag path in svg.

    Waiting for the reply, thank you!

    • He, indeed nice work!
      But I also would like to know how to get the value for “data-path-hover”. I’ve created my own SVG in Illustrator and copied the path to the “d” value. But I can’t figure out how to get the “data-path-hover”. Hope someone knows, thanks! (:

  12. Hey Mary,

    I am new to SVGs. I made an SVG that is a grouped image that features a black circle with a white accent in the bottom right corner. Is it possible to make the grouped image transition into the simple square image that you have for the hover state in the first demo? Currently my code makes the white accent turn into the white box but the black circle remains. How can I make the black circle disappear for the hover state?

  13. Awesome… just loved it. finally i got something new. Thanks for sharing !

  14. Replacing <button> with <a> tag issue.
    When i trying to change the button tag with the a tag I got an error
    “Uncaught TypeError: Cannot read property ‘attr’ of null ”
    in js line
    from : path.attr( ‘d’ ),
    How can i fix it?

  15. I’m newbie
    please help me how to use this??or how to install to my web?? this source same as like a wordpress plugin??
    thank you very much πŸ™‚

  16. Hello! very interesting. Is it possible to do something like this but fullscreen ?

  17. That’s such an Amazing job, i like that very much, i’m interested in using it but before that i’m gonna ask whether i can use it with some changes in the code, on my website?

  18. 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

    • <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>

    • Wrapping a button tag around an anchor tag will cause the link to break in older versions of IE. The first example will work in IE recent IE versions.

  19. 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

  20. 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

  21. 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.

    • I would love to see a version of this without Javascript. The goal for my website is CSS3 and HTML5 only. Simplicity!

    • Compatibility is the reason why. Not all browsers support SVG animation. Nor do they support all subsets of CSS3 — even IE9 which is currently VERY popular. And just because its CSS doesn’t mean it’s going to be simplicity or less code than javascript if your Javascript is written well.

  22. Thank you so much for this really awesome tutorial! I’m so excited about snap.svg now!

  23. Nice work ! appreciate the effort given. I’ve installed the plugin in my site works great except on responsive . The 2nd demo version is not animating on mobile. as well as on chrome for sometime. But after a quite while it starts animating on chrome. but still not on mobile devices . Have done a lot of work and now its not working on mobile. Please advice….. Or do you have a fix for this ??

  24. I’ve added this effect and made some changes to the svg path to fit my design. I have an issue that I’m not sure where to start to resolve. When you hover over the image with the added effect, the transition is a bit jumpy. Has anyone come across this issue and how did you go about fixing it?