From our sponsor: Chromatic - Visual testing for Storybook, Playwright & Cypress. Catch UI bugs before your users do.
Today we’d like to show you how to create an intriguingly simple, yet eye-catching transition effect using CSS Masks. Together with clipping, masking is another way of defining visibility and composite with an element. In the following tutorial we’ll show you how to apply the new properties for a modern transition effect on a simple slideshow. We’ll be applying animations utilizing the steps() timing function and move a mask PNG over an image to achieve an interesting transition effect.
CSS Masks
Method of displaying part of an element, using a selected image as a mask
W3C Candidate Recommendation
Supported from the following versions:
Desktop
- 120
- 53
- No
- 106
- 15
Mobile / Tablet
- 15
- 129
- No
- 129
- 130
Keep in mind that Firefox has only partial support (it only supports inline SVG mask elements) so we’ll have a fallback for now. Hopefully, we can welcome support in all modern browsers very soon. Note that we’re adding Modernizr to check for support.
So let’s get started!
Creating the Mask Image
In this tutorial we’ll be going through the first example (demo 1).
For the mask transition effect to work, we will need an image that we’ll use to hide/show certain parts of our underlying image. That mask image will be a PNG with transparent parts on it. The PNG itself will be a sprite image and it looks as follows:
While the black parts will show the current image, the white part (which is actually transparent) will be the masked part of our image that will reveal the second image.
In order to create the sprite image we will use this video. We import it into Adobe After Effects to reduce the timing of the video, remove the white part and export it as a PNG sequence.
To reduce the duration to 1.4 seconds (the time we want our transition to take) we’ll use the Time stretch effect.
To remove the white part we will use Keying -> extract and set the white point to 0. In the screenshot below, the blue portion is the background of our composition, the transparent parts of the video.
Finally, we can save our composition as a PNG sequence and then use Photoshop or a tool like this CSS sprite generator to generate a single image:
This is one sprite image for a very organic looking reveal effect. We’ll create another, “inversed” sprite for the opposite kind of effect. You’ll find all the different sprites in the img folder of the demo files.
Now, that we’ve created the mask image, let’s dive into the HTML structure for our simple example slideshow.
The Markup
For our demo, we’ll create a simple slideshow to show the mask effect. Our slideshow will fill the entire screen and we’ll add some arrows that will trigger the slide transitions. The idea is to superpose the slides and then change the z-index of the incoming slide when the animation is over. The structure for our slideshow looks as follows:
<div class="page-view">
<div class="project">
<div class="text">
<h1>“All good things are <br> wild & free”</h1>
<p>Photo by Andreas Rønningen</p>
</div>
</div>
<div class="project">
<div class="text">
<h1>“Into the wild”</h1>
<p>Photo by John Price</p>
</div>
</div>
<div class="project">
<div class="text">
<h1>“Is spring coming?”</h1>
<p>Photo by Thomas Lefebvre</p>
</div>
</div>
<div class="project">
<div class="text">
<h1>“Stay curious”</h1>
<p>Photo by Maria</p>
</div>
</div>
<nav class="arrows">
<div class="arrow previous">
<svg viewBox="208.3 352 4.2 6.4">
<polygon class="st0" points="212.1,357.3 211.5,358 208.7,355.1 211.5,352.3 212.1,353 209.9,355.1"/>
</svg>
</div>
<div class="arrow next">
<svg viewBox="208.3 352 4.2 6.4">
<polygon class="st0" points="212.1,357.3 211.5,358 208.7,355.1 211.5,352.3 212.1,353 209.9,355.1"/>
</svg>
</div>
</nav>
</div>
The division page-view is our global container, it will contain all our slides. The project divisions are the slides of our slideshow; each one contains a title and a legend. Additionally, we’ll set an individual background image for each slide.
The arrows will serve as our trigger for the next or previous animation, and to navigate through the slides.
Let’s have a look at the style.
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
In this part we’ll define the CSS for our effect.
We’ll set up the layout for a classic fullscreen slider, with some centered titles and the navigation at the bottom left of the page. Moreover, we’ll define some media queries to adapt the style for mobile devices.
Furthermore, we’ll set our sprite images as invisible backgrounds on our global container just so that we have them start loading when we open the page.
.demo-1 {
background: url(../img/nature-sprite.png) no-repeat -9999px -9999px;
background-size: 0;
}
.demo-1 .page-view {
background: url(../img/nature-sprite-2.png) no-repeat -9999px -9999px;
background-size: 0;
}
Each slide will have a different background-image:
.demo-1 .page-view .project:nth-child(1) {
background-image: url(../img/nature-1.jpg);
}
.demo-1 .page-view .project:nth-child(2) {
background-image: url(../img/nature-2.jpg);
}
.demo-1 .page-view .project:nth-child(3) {
background-image: url(../img/nature-3.jpg);
}
.demo-1 .page-view .project:nth-child(4) {
background-image: url(../img/nature-4.jpg);
}
This would of course be something that you’ll dynamically implement but we are interested in the effect, so let’s keep it simple.
We define a class named hide that we will add to the slide whenever we want to hide it. The class definition contains our sprite applied as a mask.
Knowing that a frame is 100% of the screen and our animation contains 23 images, we will need to set the width to 23 * 100% = 2300%.
Now we add our CSS animation utilizing steps. We want our sprite to stop at the beginning of our last frame. Hence, to achieve this, we need to count one less step than the total, which is 22 steps:
.demo-1 .page-view .project:nth-child(even).hide {
-webkit-mask: url(../img/nature-sprite.png);
mask: url(../img/nature-sprite.png);
-webkit-mask-size: 2300% 100%;
mask-size: 2300% 100%;
-webkit-animation: mask-play 1.4s steps(22) forwards;
animation: mask-play 1.4s steps(22) forwards;
}
.demo-1 .page-view .project:nth-child(odd).hide {
-webkit-mask: url(../img/nature-sprite-2.png);
mask: url(../img/nature-sprite-2.png);
-webkit-mask-size: 7100% 100%;
mask-size: 7100% 100%;
-webkit-animation: mask-play 1.4s steps(70) forwards;
animation: mask-play 1.4s steps(70) forwards;
}
Finally, we define the animation keyframes:
@-webkit-keyframes mask-play {
from {
-webkit-mask-position: 0% 0;
mask-position: 0% 0;
}
to {
-webkit-mask-position: 100% 0;
mask-position: 100% 0;
}
}
@keyframes mask-play {
from {
-webkit-mask-position: 0% 0;
mask-position: 0% 0;
}
to {
-webkit-mask-position: 100% 0;
mask-position: 100% 0;
}
}
And here we go; we now have a structured and styled slideshow. Let’s turn it into something functional!
The JavaScript
We will be using zepto.js for this demo which is a very lightweight JavaScript framework similar to jQuery.
We start by declaring all our variables, setting the durations and the elements.
Then we initialize the events, get the current and the next slide, set the correct z-index.
function Slider() {
// Durations
this.durations = {
auto: 5000,
slide: 1400
};
// DOM
this.dom = {
wrapper: null,
container: null,
project: null,
current: null,
next: null,
arrow: null
};
// Misc stuff
this.length = 0;
this.current = 0;
this.next = 0;
this.isAuto = true;
this.working = false;
this.dom.wrapper = $('.page-view');
this.dom.project = this.dom.wrapper.find('.project');
this.dom.arrow = this.dom.wrapper.find('.arrow');
this.length = this.dom.project.length;
this.init();
this.events();
this.auto = setInterval(this.updateNext.bind(this), this.durations.auto);
}
/**
* Set initial z-indexes & get current project
*/
Slider.prototype.init = function () {
this.dom.project.css('z-index', 10);
this.dom.current = $(this.dom.project[this.current]);
this.dom.next = $(this.dom.project[this.current + 1]);
this.dom.current.css('z-index', 30);
this.dom.next.css('z-index', 20);
};
We listen for the click event on the arrows, and if the slideshow is not currently involved in an animation, we check if the click was on the next or previous arrow. Like that we adapt the value of the “next” variable and we proceed to change the slide.
/**
* Initialize events
*/
Slider.prototype.events = function () {
var self = this;
this.dom.arrow.on('click', function () {
if (self.working)
return;
self.processBtn($(this));
});
};
Slider.prototype.processBtn = function (btn) {
if (this.isAuto) {
this.isAuto = false;
clearInterval(this.auto);
}
if (btn.hasClass('next'))
this.updateNext();
if (btn.hasClass('previous'))
this.updatePrevious();
};
/**
* Update next global index
*/
Slider.prototype.updateNext = function () {
this.next = (this.current + 1) % this.length;
this.process();
};
/**
* Update next global index
*/
Slider.prototype.updatePrevious = function () {
this.next--;
if (this.next < 0)
this.next = this.length - 1;
this.process();
};
This function is the heart of our slideshow: we set the class “hide” to the current slide and once the animation is over, we reduce the z-index of the previous slide, increase the one of the current slide and then remove the hide class of the previous slide.
/**
* Process, calculate and switch between slides
*/
Slider.prototype.process = function () {
var self = this;
this.working = true;
this.dom.next = $(this.dom.project[this.next]);
this.dom.current.css('z-index', 30);
self.dom.next.css('z-index', 20);
// Hide current
this.dom.current.addClass('hide');
setTimeout(function () {
self.dom.current.css('z-index', 10);
self.dom.next.css('z-index', 30);
self.dom.current.removeClass('hide');
self.dom.current = self.dom.next;
self.current = self.next;
self.working = false;
}, this.durations.slide);
};
Adding the respective classes will trigger our animations which in turn apply the mask image to our slides. The main idea is to move the mask in a step animation function in order to create transition flow.
And that’s it! I hope you find this tutorial useful and have fun creating your own cool mask effects! Don’t hesitate to share your creations, I would love to see them!
Browser Support:- ChromeSupported
- FirefoxNot supported
- Internet ExplorerNot supported
- SafariSupported
- OperaSupported
Good job 🙂
Work well on Chrome but not in FF (v47.0.1)
Great Thx man !
I will try it for the 2016 Codevember challenge !
Nice effects and idea. On a 4k Monitor choppy even with a GTX1080 on Chrome, but very nice to watch on 1920×1040.
🙂
Nice!!! Congrats!!
Very nice effect to watch in the demo – good work. However, when I implement it the transition between slides is jerky even in chrome.
Great animation Roben, but the Codrops site is too slow 🙁
nice one.thanks
Firefox 49.0.1 only shows fallback version.
Good to see this post, I am very much interested in css transition effects it makes website good look
Hi! Can we use some of your code for our project. Our project is something like a page builder and we want to sell our projects when it is finished. Thank you and God bless!
Hi Jayson, please have a look here. Cheers.
Its an awesome work indeed!
Could a canvas animation be used instead?
All transition effects are just an awesome. Thank you for sharing.
amazing !
I am currently struggling in designing! because I am a developer and not much of a designer for a Professional Website Development Company. Thanks for the tips.
Providing a way to control animation speed when changing CSS properties. Great!
#convertbetter
thank you, Robin! perfectly method and really good suitable for unadvanced dev)
Awesome library! How would I change the CSS to make this slider a header for a one-page website? Is there anywhere I can style this as a fixed hero on a larger page?
What a fantastic resource. Thanks
amazing job ^^
Awesome !!!
Nice tutorial, but its not very well explained how the transition mask itself is created, i understand the part how to do the animation. But creating the shape and animating seems to be missing
Magnificent! Thanks a lot for this tutoria!!!
Cool effect! Going to try to incorporate it into my next web design project. Thanks.
Brilliant! This is awesome
plz help me. how to support in IE browser
any body der…
use flash
This is absolutely amazing ! Thanks a lot Robin !!
I tested it on Chrome ver(60,0,3112,116) on an Android phone and it works fine.
maybe preload the images so they dont snap into view?
How would you do the same effect but without the mask so the ink png animates and the next slide reveals?
i tried it in my local host for a website, but the mask effect is not working. just images and text are changing. nothing else 🙁