We’ve been publishing in our library some nice mask effects powered by SVG properties. This time we took advantage of the PNG transparencies to zoom through the mask layer, into a project background image.
If you want to change the color of the .png masks, you can easily do it in Photoshop (or any other graphic tool) by applying a color overlay to the whole image layer. If you plan to create your own masks, please note that this effect works only if there’s an empty space in the very center of the mask.
Inspiration: Offsite Homepage Animation by Hrvoje Grubisic.
Images: Unsplash
Creating the structure
The HTML structure is composed of a list of <section>
s wrapped in a .cd-image-mask-effect
element. Each <section>
contains a div.featured-image
(project image), a div.mask
(image mask) and a div.cd-project-info
for the project content.
<section class="project-1 cd-project-mask">
<h1>Project Name</h1>
<div class="featured-image"></div>
<div class="mask">
<img src="img/mask-01.png" alt="mask">
<span class="mask-border mask-border-top"></span>
<span class="mask-border mask-border-bottom"></span>
<span class="mask-border mask-border-left"></span>
<span class="mask-border mask-border-right"></span>
</div>
<a href="#0" class="project-trigger">Explore Project</a>
<a href="#0" class="cd-scroll cd-img-replace">Scroll down</a>
<div class="cd-project-info" data-url="project-1">
<!-- content loaded using js -->
</div>
<a href="#0" class="project-close cd-img-replace">Close Project</a>
</section> <!-- .cd-project-mask -->
<section class="project-2 cd-project-mask">
<!-- content here -->
</section>
<!-- other sections here -->
The project content is not included in the HTML but is loaded using JavaScript.
Adding style
Each .cd-project-mask
has a height of 100vh (viewport height) and a width of 100%; the project image is set as background-image of the .featured-image
element, while the mask image is wrapped inside the .mask
element.
Four .mask-border
elements have been used to create a frame around the image mask to make sure the project featured image is not visible outside the mask (we used <span> elements rather than pseudo elements because their behaviour was buggy on Safari 9).
.cd-project-mask {
position: relative;
height: 100vh;
width: 100%;
overflow: hidden;
}
.cd-project-mask .featured-image {
/* project intro image */
position: absolute;
left: 50%;
top: 50%;
bottom: auto;
right: auto;
transform: translateX(-50%) translateY(-50%);
height: 100%;
width: 100%;
background: url(../img/img-01.jpg) no-repeat center center;
background-size: cover;
}
.cd-project-mask .mask {
position: absolute;
left: 50%;
top: 50%;
bottom: auto;
right: auto;
transform: translateX(-50%) translateY(-50%);
width: 300px;
height: 300px;
}
.cd-project-mask .mask .mask-border {
/* this is used to create a frame around the mask */
position: absolute;
}
.cd-project-mask .mask .mask-border-top,
.cd-project-mask .mask .mask-border-bottom {
/* this is used to create a frame around the mask */
height: calc(50vh - 150px + 10px);
width: 100vw;
left: 50%;
right: auto;
transform: translateX(-50%);
}
.cd-project-mask .mask .mask-border-top {
bottom: calc(100% - 10px);
}
.cd-project-mask .mask .mask-border-bottom {
top: calc(100% - 10px);
}
.cd-project-mask .mask .mask-border-left,
.cd-project-mask .mask .mask-border-right {
/* this is used to create a frame around the mask */
height: 100vh;
width: calc(50vw - 150px + 10px);
top: 50%;
bottom: auto;
transform: translateY(-50%);
}
.cd-project-mask .mask .mask-border-left {
left: calc(100% - 10px);
}
.cd-project-mask .mask .mask-border-right {
right: calc(100% - 10px);
}
When the user selects a project, the class .project-view
(added to the wrapper .cd-image-mask-effect
) is used to hide all the other projects.
The .mask
element is then scaled up to reveal the project featured image and the project content is loaded (more in the Events handling section).
.project-view .cd-project-mask:not(.project-selected) {
/* the project-view class is added to the .cd-image-mask-effect element when a project is selected - hide all not selected projects */
position: absolute;
top: 0;
left: 0;
opacity: 0;
visibility: hidden;
}
Events handling
To implement this image mask effect, we created a ProjectMask
object and used the initProject
method to attach the proper event handlers.
function ProjectMask( element ) {
this.element = element;
this.projectTrigger = this.element.find('.project-trigger');
this.projectClose = this.element.find('.project-close');
this.projectTitle = this.element.find('h1');
this.projectMask = this.element.find('.mask');
//...
this.initProject();
}
var revealingProjects = $('.cd-project-mask');
var objProjectMasks = [];
if( revealingProjects.length > 0 ) {
revealingProjects.each(function(){
//create ProjectMask objects
objProjectMasks.push(new ProjectMask($(this)));
});
}
When the user selects a project, the revealProject
method is used to scale up the mask image while the uploadContent
method takes care of loading the project content (using the load()
function) and adding the new page to the window.history (using the pushState()
method).
ProjectMask.prototype.initProject = function() {
var self = this;
//open the project
this.projectTrigger.on('click', function(event){
event.preventDefault();
if( !self.animating ) {
self.animating = true;
//upload project content
self.uploadContent();
//show project content and scale up mask
self.revealProject();
}
});
//...
};
ProjectMask.prototype.revealProject = function() {
var self = this;
//get mask scale value
self.updateMaskScale();
//scale up mask and animate project title
self.projectTitle.attr('style', 'opacity: 0;');
self.projectMask.css('transform', 'translateX(-50%) translateY(-50%) scale('+self.maskScaleValue+')').one(transitionEnd, function(){
self.element.addClass('center-title');
self.projectTitle.attr('style', '');
self.animating = false;
});
//hide the other sections
self.element.addClass('project-selected content-visible').parent('.cd-image-mask-effect').addClass('project-view');
}
ProjectMask.prototype.uploadContent = function(){
var self = this;
//if content has not been loaded -> load it
if( self.projectContent.find('.content-wrapper').length == 0 ) self.projectContent.load(self.projectContentUrl+'.html .cd-project-info > *');
if( self.projectContentUrl+'.html'!=window.location ){
//add the new page to the window.history
window.history.pushState({path: self.projectContentUrl+'.html'},'',self.projectContentUrl+'.html');
}
}