How to Create a Cover Page Transition

0
9


From our sponsor: B25600467Try Mailchimp today.

The other day I saw this nice dribble shot by Vitalii Burhonskyi that shows several lovely transitions. One of them is what I would call a “cover transition” where a black cover animates to hide some content and then some new content reveals on top of the cover (which is of a different color than the previous view).

I love the fact that we can have a plethora of different animations here to “unreveal” or show the new content. So I’ve created a sequence that we’ll explore today in a short tutorial where we’ll have a look at some highlights of the structure and animations.

We’ll be using GSAP from GreenSock as animation library.

Markup and Styles

Our initial view will show three items in a grid structure:

CoverTransition01

Let’s have a look at the markup for this. We’ll have a division for all items:

<div class="content">
	<div class="item">
		<span class="item__meta">2020</span>
		<h2 class="item__title">Alex Moulder</h2>
		<div class="item__img"><div class="item__img-inner" style="background-image:url(img/1.jpg)"></div></div>
		<p class="item__desc">I am only waiting for love to give myself up at last into his hands. That is why it is so late and why I have been guilty of such omissions.</p>
		<a class="item__link">view</a>
	</div>
	<div class="item">
		<span class="item__meta">2021</span>
		<h2 class="item__title">Aria Bennett</h2>
		<div class="item__img"><div class="item__img-inner" style="background-image:url(img/2.jpg)"></div></div>
		<p class="item__desc">They come with their laws and their codes to bind me fast; but I evade them ever, for I am only waiting for love to give myself up at last into his hands.</p>
		<a class="item__link">view</a>
	</div>
	<div class="item">
		<span class="item__meta">2022</span>
		<h2 class="item__title">Jimmy Hughes</h2>
		<div class="item__img"><div class="item__img-inner" style="background-image:url(img/3.jpg)"></div></div>
		<p class="item__desc">Clouds heap upon clouds and it darkens. Ah, love, why dost thou let me wait outside at the door all alone?</p>
		<a class="item__link">view</a>
	</div>
</div>

First, our whole page will be of a grid layout:

main 
	padding: 1.5rem 2.5rem 3rem;
	height: 100vh;
	display: grid;
	grid-template-columns: 100%;
	grid-template-areas: 'frame' 'content';
	grid-template-rows: min-content 1fr;
	grid-row-gap: 8vh;

The content division will have the following style to show the items in a grid:

.content 
	grid-area: content;
	max-width: 400px;


@media screen and (min-width: 53em) 
	.content 
		max-width: none;
		display: grid;
		grid-template-columns: repeat(3,1fr);
		grid-template-rows: 100%;
		grid-column-gap: 5vw;
	

We only want to show the items side-by-side when we are on a large screen. So we add a media query.

For the item’s inner elements we’ll have the following style:

.item 
	margin-bottom: 5rem;
	display: grid;
	grid-template-columns: 100%;
	grid-template-rows: 1rem auto auto 1fr auto;


.item__title 
	font-family: kudryashev-d-excontrast-sans, sans-serif;
	font-weight: 300;
	font-size: 2rem;
	margin-bottom: 0.5rem;


.item__img 
	position: relative;
	overflow: hidden;
	width: 100%;
	aspect-ratio: 500/333;


.item__img-inner 
	background-position: 50% 45%;
	background-size: cover;
	width: 100%;
	height: 100%;


.item__desc 
	margin-top: 2.5rem;
	line-height: 1.1;


.item__link 
	cursor: pointer;
	text-transform: lowercase;
	width: 100%;
	padding: 1rem;
	color: var(--color-text);
	border: 1px solid var(--color-border);
	border-radius: 2rem;
	text-align: center;


.item__link:hover 
	background: var(--color-text);
	border-color: var(--color-text);
	color: var(--color-text-alt);


@media screen and (min-width: 53em) 
	.item 
		margin-bottom: 0;
	
	.item__title 
		font-size: clamp(1.25rem,3vw,2rem);
	

The image element has a nested structure that will allow us to make a little zoom effect. An interesting thing here is the aspect-ratio property that allows us to set responsive image dimensions according to its real size while using the background-image property.

When we click on the item button, we’ll show a cover animation. This will be the two elements animating their scale transform to cover the entire page:

<div class="overlay">
	<div class="overlay__row"></div>
	<div class="overlay__row"></div>
</div>

Let’s add the following style:

.overlay 
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	display: grid;
	grid-template-columns: 100%;
	pointer-events: none;
	grid-template-rows: repeat(2,1fr);


.overlay__row 
	background: var(--color-overlay);
	transform: scaleY(0);
	will-change: transform;


.overlay__row:first-child 
	transform-origin: 50% 0%;


.overlay__row:last-child 
	transform-origin: 50% 100%;

Setting the right transform origins for each one of the “rows” will make sure that they animate from up and from down, “closing” the current view and hiding it.

Next, let’s have a look at the view we will see later. This section will be called “previews” and we’ll add the following content:

<section class="previews">
	<div class="preview">
		<div class="preview__img"><div class="preview__img-inner" style="background-image:url(img/1_big.jpg)"></div></div>
		<h2 class="preview__title oh"><span class="oh__inner">Moulder</span></h2>
		<div class="preview__column preview__column--start">
			<span class="preview__column-title preview__column-title--main oh"><span class="oh__inner">Alex Moulder</span></span>
			<span class="oh"><span class="oh__inner">2020</span></span>
		</div>
		<div class="preview__column">
			<h3 class="preview__column-title oh"><span class="oh__inner">Location</span></h3>
			<p>And if it rains, a closed car at four. And we shall play a game of chess, pressing lidless eyes and waiting for a knock upon the door.</p>
		</div>
		<div class="preview__column">
			<h3 class="preview__column-title oh"><span class="oh__inner">Material</span></h3>
			<p>At the violet hour, when the eyes and back, turn upward from the desk, when the human engine waits.</p>
		</div>
		<button class="unbutton preview__back"><svg width="100px" height="18px" viewBox="0 0 50 9"><path vector-effect="non-scaling-stroke" d="m0 4.5 5-3m-5 3 5 3m45-3h-77"></path></svg></button>
	</div>

	<!-- preview -->

	<!-- preview -->
	
</section>
CoverTransition02

The large image will animate with a reveal/unreveal animation that is explained in detail in this tutorial. That’s why we use a nested structure just like on the item image. For the texts that we want to show by translating (and being cut off by the parent), we’ll use the .oh > .oh__inner structure. The idea behind this is to translate the oh_inner element to hide it. For multi-line text we will add this structure dynamically with JavaScript. The paragraphs in our preview__column divisions will be broken into lines using SplitType.

Let’s add the following styles for the line magic to work:

.oh 
	position: relative;
    overflow: hidden;


.oh__inner 
	will-change: transform;
    display: inline-block;


.line 
	transform-origin: 0 50%;
	white-space: nowrap;
	will-change: transform;

Now, let’s get this baby animated.

The JavaScript

Let’s define and instantiate some things first:

import  gsap  from 'gsap';
import  Item  from './item';
import  Preview  from './preview';

// body element
const body = document.body;

// .content element
const contentEl = document.querySelector('.content');

// frame element
const frameEl = document.querySelector('.frame');

// top and bottom overlay overlay elements
const overlayRows = [...document.querySelectorAll('.overlay__row')];

// Preview instances array
const previews = [];
[...document.querySelectorAll('.preview')].forEach(preview => previews.push(new Preview(preview)));

// Item instances array
const items = [];
[...document.querySelectorAll('.item')].forEach((item, pos) => items.push(new Item(item, previews[pos])));

Now, when we open an item, we’ll first set our content to not be clickable anymore. That’s done with a class.

Then we hide all those lines and elements that we want to animate in once we show the preview content. The preview-visible class helps us set some colors and pointer events. We also use it to hide our little frame at the top of the page, so that we can then show it again with an animation once the cover hides the initial view.

The image gets revealed by translating the image element in one direction and the inner element (which actually contains the background-image) in the opposite direction.

We also show all the lines and oh__inner elements finally.

const openItem = item => 
    
    gsap.timeline(
        defaults: 
            duration: 1, 
            ease: 'power3.inOut'
        
    )
    .add(() => 
        // pointer events none to the content
        contentEl.classList.add('content--hidden');
    , 'start')

    .addLabel('start', 0)
    .set([item.preview.DOM.innerElements, item.preview.DOM.backCtrl], 
        opacity: 0
    , 'start')
    .to(overlayRows, 
        scaleY: 1
    , 'start')

    .addLabel('content', 'start+=0.6')

    .add(() => 
        body.classList.add('preview-visible');

        gsap.set(frameEl, 
            opacity: 0
        , 'start')
        item.preview.DOM.el.classList.add('preview--current');
    , 'content')
    // Image animation (reveal animation)
    .to([item.preview.DOM.image, item.preview.DOM.imageInner], 
        startAt: y: pos => pos ? '101%' : '-101%',
        y: '0%'
    , 'content')
    
    .add(() => 
        for (const line of item.preview.multiLines) 
            line.in();
        
        gsap.set(item.preview.DOM.multiLineWrap, 
            opacity: 1,
            delay:0.1
        )
    , 'content')
    // animate frame element
    .to(frameEl, 
        ease: 'expo',
        startAt: y: '-100%', opacity: 0,
        opacity: 1,
        y: '0%'
    , 'content+=0.3')
    .to(item.preview.DOM.innerElements, 
        ease: 'expo',
        startAt: yPercent: 101,
        yPercent: 0,
        opacity: 1
    , 'content+=0.3')
    .to(item.preview.DOM.backCtrl, 
        opacity: 1
    , 'content')

;

When we close the preview, we’ll need to do some reverse animations:

const closeItem = item => 
    
    gsap.timeline(
        defaults: 
            duration: 1, 
            ease: 'power3.inOut'
        
    )
    .addLabel('start', 0)
    .to(item.preview.DOM.innerElements, 
        yPercent: -101,
        opacity: 0,
    , 'start')
    .add(() => 
        for (const line of item.preview.multiLines) 
            line.out();
        
    , 'start')
    
    .to(item.preview.DOM.backCtrl, 
        opacity: 0
    , 'start')

    .to(item.preview.DOM.image, 
        y: '101%'
    , 'start')
    .to(item.preview.DOM.imageInner, 
        y: '-101%'
    , 'start')
    
    // animate frame element
    .to(frameEl, 
        opacity: 0,
        y: '-100%',
        onComplete: () => 
            body.classList.remove('preview-visible');
            gsap.set(frameEl, 
                opacity: 1,
                y: '0%'
            )
        
    , 'start')

    .addLabel('grid', 'start+=0.6')

    .to(overlayRows, 
        //ease: 'expo',
        scaleY: 0,
        onComplete: () => 
            item.preview.DOM.el.classList.remove('preview--current');
            contentEl.classList.remove('content--hidden');
        
    , 'grid')
;

Let’s not forget the eventListeners:

for (const item of items) 
    // Opens the item preview
    item.DOM.link.addEventListener('click', () => openItem(item));
    // Closes the item preview
    item.preview.DOM.backCtrl.addEventListener('click', () => closeItem(item));

This is how it all comes together:

You can do a lot of different things here, either to animate the content in or out. The key is to keep things interesting when animating in and do a swift closing animation so that the user doesn’t have to wait too long for the initial view to come back 😉 Now it’s your turn! Explore the code and try to do some other animations, timing and easings to give it another feel and see what works and what doesn’t! Hope you have fun!

Thanks for checking by ✌️ Hit me up if you want to work with me!

How to Map Texture to a 3D Face with Three.js

The Comeback of Maximalism and What it Could Mean for Web Design

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here