<div class="stage">
<svg class="js-svg svg">
<path class="js-dynamic-path" d=""></path>
</svg>
<div class="scene js-scene scene-1 js-scene-1">
<div class="header js-header">
<div class="container">
<form autocomplete="off">
<div class="field-container js-component">
<label for="name">Pseudo</label>
<input type="text" id="name" autocomplete="new-pseudo" placeholder="Quel est votre petit nom?">
</div>
<div class="field-container js-component">
<label for="mail">Mail</label>
<input type="email" id="mail" autocomplete="new-email">
</div>
<div class="field-container js-component">
<label for="password">Mot de passe</label>
<input type="password" id="password" autocomplete="off">
<p class="input-helper">Le mot de passe doit contenir 5 caractères minimum</p>
</div>
</form>
</div>
</div>
</div>
<div class="scene js-scene scene-2 js-scene-2">
<div class="container">
<div class="cards-stack-questions-container">
<div class="card-title js-component">Ma localisation</div>
<div class="card-subtitle js-component">Dans quelle ville habitez vous?</div>
<div class="js-card card">
<form>
<div class="field-container js-component">
<label for="city">Ville</label>
<input type="text" id="city">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<button type="button" class="next js-next">
Morph
</button>
$color-blue: #18c5d6;
body {
overflow: hidden;
margin: 0;
font-family: 'Ubuntu', sans-serif;
}
.container {
max-width: 300px;
margin: 0 auto;
padding: 0 15px;
}
.scene {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
&.hidden {
opacity: 0;
pointer-events: none;
}
}
.header {
position: relative;
padding-top: 20px;
}
.input-helper {
color: white;
margin: 5px 0 0;
font-size: 12px;
text-align: center;
}
.field-container {
margin-bottom: 20px;
&:last-of-type {
margin-bottom: 0;
}
label {
text-transform: uppercase;
display: block;
opacity: .5;
cursor: pointer;
font-size: .75rem;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 10px 5px;
border-radius: 10px;
border: 0;
box-sizing: border-box;
}
}
.svg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
opacity: 1;
path {
fill: $color-blue;
}
.js-card-path {
fill: green;
}
.play {
fill: red;
}
}
.title {
text-align: center;
font-size: 2rem;
color: $color-blue;
margin: 3rem 0 .5rem;
}
.subtitle {
text-align: center;
font-weight: 700;
margin-bottom: 3rem;
}
.card {
// background-color: $color-blue;
padding: 15px;
height: 300px;
width: 300px;
box-sizing: border-box;
border-radius: 15px;
position: absolute;
}
.next {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
border-width: 0;
padding: 15px;
background: linear-gradient(to right, #f4630a 0%,#f77e35 100%);
color: white;
font-size: 1.25rem;
cursor: pointer;
}
.card-title {
font-family: 'Playfair Display', serif;
font-style: italic;
font-size: 2rem;
color: $color-blue;
text-align: center;
margin-bottom: 10px;
margin-top: 2rem;
}
.card-subtitle {
font-weight: bold;
text-align: center;
font-size: 1.1rem;
margin-bottom: 2rem;
}
View Compiled
const headerEl = document.querySelector('.js-header');
const cardEl = document.querySelector('.js-card');
const nextBtnEl = document.querySelector('.js-next');
const SVGEl = document.querySelector('.js-svg');
const pathEl = SVGEl.querySelector('.js-dynamic-path');
class Scene {
constructor({container, computePath}) {
this.tween = null;
this.container = container;
this.computePath = computePath;
this.components = container.querySelectorAll('.js-component');
this.container.style.pointerEvents = "none"
this.components.forEach((el) => {
el.style.opacity = 0;
el.style.transform = 'translate(0, 30px)';
})
}
killTween() {
if (this.tween) {
this.tween.kill();
}
}
animateIn() {
this.animate(1, 0, () => {this.container.style.pointerEvents = "all"});
}
animateOut(callBack) {
this.animate(0, 30, callBack);
}
animate(opacity, y, onComplete) {
this.killTween();
TweenMax.staggerTo(this.components, .5, {
opacity,
y,
ease: Power4.easeOut,
}, 0.1, onComplete);
}
}
class SceneManager {
constructor() {
this.scenes = [];
this.TL = new TimelineMax();
this.activeSceneIndex = 0;
}
addScene(html, computePath) {
this.scenes.push(new Scene({container: html, computePath}));
}
getNextIndex() {
return this.activeSceneIndex < this.scenes.length - 1 ? this.activeSceneIndex + 1 : 0;
}
getPath() {
return this.scenes[this.activeSceneIndex].computePath();
}
getNextPath() {
return this.scenes[this.getNextIndex()].computePath();
}
morph(onComplete) {
this.TL.clear();
this.TL.to(pathEl, 0.2, {
morphSVG: sceneManager.getNextPath(),
ease: Power2.easeIn,
})
.to(pathEl, 0.1, {
scale: 0.9,
transformOrigin: '50% 50%',
})
.to(pathEl, .5, {
scale: 1,
ease: Elastic.easeOut.config(1, 1),
onComplete
})
}
next() {
this.scenes[this.activeSceneIndex].animateOut(() => {
this.morph(() => {
this.activeSceneIndex = this.getNextIndex();
this.scenes[this.activeSceneIndex].animateIn();
})
});
}
init() {
this.scenes[this.activeSceneIndex].animateIn();
}
}
const sceneManager = new SceneManager();
sceneManager.addScene(document.querySelector('.js-scene-1'), () => {
const bezierHandleDistance = 40;
const headerDomRect = headerEl.getBoundingClientRect();
const x = headerDomRect.x;
const y = headerDomRect.y;
const w = headerDomRect.width;
const h = headerDomRect.height;
let d = `M ${x} ${y}`; // H
d += `Q ${x} ${y} ${x} ${y}`; // A
d += `L ${x + w} ${y}`; // B
d += `Q ${x + w} ${y} ${x + w} ${y}`; // C
d += `L ${x + w} ${y + h}`; // D
d += `Q ${x + w} ${y + h + bezierHandleDistance} ${w / 2} ${y + h + bezierHandleDistance}`; // E
d += `L ${w / 2} ${y + h + bezierHandleDistance}`; // F
d += `Q ${x} ${y + h + bezierHandleDistance} ${x} ${y + h}`; // G
d += `Z`; // Close path
return d;
});
sceneManager.addScene(document.querySelector('.js-scene-2'),() => {
const r = 15;
const cardDomRect = cardEl.getBoundingClientRect();
const x = cardDomRect.x;
const y = cardDomRect.y;
const w = cardDomRect.width;
const h = cardDomRect.height;
let d = `M ${x} ${y + r}`; // H
d += `Q ${x} ${y} ${x + r} ${y}`; // A
d += `L ${x + w - r} ${y}`; // B
d += `Q ${x + w} ${y} ${x + w} ${y + r}` // C
d += `L ${x + w} ${y + h - r}`; // D
d += `Q ${x + w} ${y + h} ${x + w - r} ${y + h}`; // E
d += `L ${x + r} ${y + h}`; // F
d += `Q ${x} ${y + h} ${x} ${y + h - r}`; // G
d += `Z`; // Close path
return d;
});
sceneManager.init();
window.onresize = resizeHandler;
function resizeHandler() {
SVGEl.setAttribute('width', `${window.innerWidth}px`);
SVGEl.setAttribute('height', `${window.innerHeight}px`);
pathEl.setAttribute('d', sceneManager.getPath());
}
resizeHandler();
nextBtnEl.addEventListener('click', () => {
sceneManager.next();
});
This Pen doesn't use any external CSS resources.