<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
<div class="pen">
<div class="controls">
<button class="modal-trigger">Open from here</button>
<button class="modal-trigger">Open from here</button>
<button class="modal-trigger">Open from here</button>
</div>
<div class="overlay cancel-modal"></div>
<div class="modal-container cancel-modal">
<div class="modal">
<footer>
<button class="submit"></button>
<button class="cancel cancel-modal"></button>
</footer>
</div>
</div>
</div>
<a class="created-by" target="_blank" href="https://popmotion.io">
Pen created with
<svg class="logo" width="125" height="25" viewBox="0 0 200 41">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="popmotion-gradient">
<stop stop-color="#ED2754" offset="0%"/>
<stop stop-color="#FF44D1" offset="100%"/>
</linearGradient>
</defs>
<path
fill="url(#popmotion-gradient)"
d="M15.65.85c6.3 0 9.6 4.17 9.6 9.4 0 5.93-4.3 11.33-11.78 11.33H8.8l-1.04 10.2H.28L3.48.85h12.17zm-5.4 7.03l-.77 7.03h4.26c2.54 0 4.04-1.67 4.04-3.9 0-1.58-1-3.12-3.13-3.12h-4.4zM23.36 21.85c0-7 5.85-12.2 12.8-12.2 5.98 0 10.65 4.4 10.65 10.43 0 7.03-6.02 12.15-12.78 12.15-6.12 0-10.66-4.35-10.66-10.38zm16.28-1.27c0-2.4-1.58-4.4-4.03-4.4-2.85 0-5.03 2.4-5.03 5.2 0 2.33 1.55 4.32 4 4.32 2.9 0 5.07-2.4 5.07-5.12zM55.66 10.1l.1 2.3s1.86-2.75 6.12-2.75c4.94 0 8.8 4.17 8.8 10.25 0 7.03-5.36 12.33-11.02 12.33-3.9 0-5.5-2.35-5.5-2.35l-1.13 10.97h-7.2l3.2-30.75h6.63zm8 10.3c0-2.32-1.55-4.22-4.14-4.22-2.77 0-4.13 2.18-4.13 2.18l-.55 5.12s.9 2.18 3.67 2.18c2.95 0 5.13-2.45 5.13-5.26zM91.18 31.78h-7.2l1.35-12.8c0-.13.05-.4.05-.67 0-1.26-.64-1.98-1.95-1.98-1.68 0-3.54 1.67-3.54 1.67l-1.46 13.78h-7.2L73.5 10.1h6.8l.1 2.18s2.98-2.54 6.3-2.54c3.53 0 4.66 2.63 4.66 2.63s3.27-2.63 6.94-2.63c4.86 0 7.08 2.72 7.08 7.12 0 .54-.05 1.13-.1 1.68l-1.35 13.24H96.7l1.33-12.38c.05-.45.05-.64.05-.9 0-1.5-.77-2.18-1.95-2.18-1.77 0-3.5 1.72-3.5 1.72l-1.45 13.74M106.16 21.85c0-7 5.85-12.2 12.8-12.2 5.98 0 10.65 4.4 10.65 10.43 0 7.03-6.02 12.15-12.78 12.15-6.13 0-10.66-4.35-10.66-10.38zm16.28-1.27c0-2.4-1.6-4.4-4.03-4.4-2.85 0-5.03 2.4-5.03 5.2 0 2.33 1.54 4.32 4 4.32 2.9 0 5.07-2.4 5.07-5.12zM140.4 10.1l.5-4.76h-7.2l-.5 4.76h-3.13l-.68 6.53h3.12l-1.6 15.15h7.23l1.58-15.15h5.18l-1.57 15.15h7.26l2.25-21.68H140.4M146.46.9l-.64 6.16h7.5l.62-6.16h-7.48M153.05 21.85c0-7 5.85-12.2 12.8-12.2 5.97 0 10.65 4.4 10.65 10.43 0 7.03-6.04 12.15-12.8 12.15-6.12 0-10.65-4.35-10.65-10.38zm16.28-1.27c0-2.4-1.6-4.4-4.04-4.4-2.86 0-5.04 2.4-5.04 5.2 0 2.33 1.54 4.32 4 4.32 2.9 0 5.07-2.4 5.07-5.12zM198 31.78h-7.2l1.26-12.02c.05-.4.05-.63.05-.95 0-1.5-.76-2.62-2.62-2.62-2.4 0-4.36 1.95-4.36 1.95l-1.4 13.65h-7.2l2.25-21.68h6.9v2.3s2.9-2.7 6.44-2.7c3.95 0 7.3 2.53 7.3 7.75 0 .4-.04 1-.08 1.5L198 31.77"
/>
</svg>
</a>
body {
--pink: #ED2754;
background: #111;
color: #eee;
font-family: 'Source Sans Pro', sans-serif;
height: 100vh;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
}
button {
outline: none;
padding: 20px;
appearance: none;
background: white;
border: none;
}
.pen {
flex: 1 1 100%;
}
.created-by {
flex: 0 0 50px;
color: #eee;
text-decoration: none;
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 30px;
border-top: 1px solid var(--pink);
}
.logo {
margin-left: 10px;
}
.controls {
display: flex;
flex-direction: column;
width: 200px;
height: 100%;
min-height: 400px;
margin: 0 auto;
justify-content: space-around;
button {
margin: 20px;
}
}
.modal-container {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
display: none;
justify-content: center;
align-items: center;
}
.overlay {
display: none;
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0,0,0,0.6);
opacity: 0;
transform: translateZ(0);
}
.modal {
background: #fff;
border-radius: 10px;
width: 300px;
height: 250px;
display: flex;
align-items: flex-end;
footer {
width: 100%;
display: flex;
justify-content: flex-end;
}
button {
border: none;
border-radius: 5px;
padding: 25px 50px;
margin: 0 10px 10px 0;
}
}
.submit {
background: #8CDA26;
}
.cancel {
background: #DA2632;
}
View Compiled
const { css, transform, chain, delay, tween, easing, parallel } = window.popmotion;
const { interpolate } = transform;
let trigger;
let isClosing = false;
// Select DOM
const modalTriggersDom = document.querySelectorAll('.modal-trigger');
const dimmer = document.querySelector('.overlay');
const modalContainer = document.querySelector('.modal-container');
const modal = document.querySelector('.modal');
// Create CSS renderers
const dimmerRenderer = css(dimmer);
const modalContainerRenderer = css(modalContainer);
const modalRenderer = css(modal);
// Return the center x, y of a bounding box
function findCenter({ top, left, height, width }) {
return {
x: left + (width / 2),
y: top + (height / 2)
};
}
/*
Generate a function that will take a progress value (0 - 1)
and use that to tween the modal from the source to the destination
bounding boxes
*/
const vRange = [0, 1];
function generateModalTweener(sourceBBox, destinationBBox) {
const sourceCenter = findCenter(sourceBBox);
const destinationCenter = findCenter(destinationBBox);
const toX = interpolate(vRange, [sourceCenter.x - destinationCenter.x, 0]);
const toY = interpolate(vRange, [sourceCenter.y - destinationCenter.y, 0]);
const toScaleX = interpolate(vRange, [sourceBBox.width / destinationBBox.width, 1]);
const toScaleY = interpolate(vRange, [sourceBBox.height / destinationBBox.height, 1]);
return (v) => modalRenderer.set({
opacity: v,
x: toX(v),
y: toY(v),
scaleX: toScaleX(v),
scaleY: toScaleY(v)
});
}
function openModal(e) {
if (e.target && e.target.classList.contains('modal-trigger')) {
trigger = e.target;
// Get bounding box of triggering element
const triggerBBox = trigger.getBoundingClientRect();
// Temporarily show modal container to measure modal
dimmerRenderer.set('display', 'block').render();
modalContainerRenderer.set('display', 'flex').render();
modalRenderer.set('opacity', 0).render();
// Get bounding box of final modal position
const modalBBox = modal.getBoundingClientRect();
// Get a function to tween the modal from the trigger
const modalTweener = generateModalTweener(triggerBBox, modalBBox);
// Fade in overlay
tween({
duration: 200,
onUpdate: (v) => dimmerRenderer.set('opacity', v)
}).start();
chain([
delay(75),
tween({
duration: 200,
ease: easing.easeOut,
onUpdate: modalTweener
})
]).start();
}
}
function closeComplete() {
isClosing = false;
dimmerRenderer.set('display', 'none').render();
modalContainerRenderer.set('display', 'none').render();
modalRenderer.set({
y: 0,
scaleX: 1,
scaleY: 1,
transformOrigin: '50% 50%'
});
}
function cancelModal(e) {
if (e.target && e.target.classList.contains('cancel-modal') && !isClosing) {
e.stopPropagation();
isClosing = true;
const triggerBBox = trigger.getBoundingClientRect();
const modalBBox = modal.getBoundingClientRect();
const modalTweener = generateModalTweener(triggerBBox, modalBBox);
parallel([
tween({
from: dimmerRenderer.get('opacity'),
to: 0,
onUpdate: (v) => dimmerRenderer.set('opacity', v)
}),
tween({
from: modalRenderer.get('opacity'),
to: 0,
duration: 250,
onUpdate: modalTweener,
onComplete: closeComplete
})
]).start();
}
}
function submitModal(e) {
if (isClosing) return;
e.stopPropagation();
isClosing = true;
const toScaleXIn = interpolate(vRange, [1, 1.2]);
const toScaleYIn = interpolate(vRange, [1, 0.8]);
const toScaleXOut = interpolate(vRange, [1.2, 0.5]);
const toScaleYOut = interpolate(vRange, [0.8, 2]);
chain([
tween({
onStart: () => modalRenderer.set('transform-origin', '50% 100%'),
duration: 200,
onUpdate: (v) => modalRenderer.set({
scaleX: toScaleXIn(v),
scaleY: toScaleYIn(v),
y: v * 100
})
}),
parallel([
tween({
from: dimmerRenderer.get('opacity'),
to: 0,
onUpdate: (v) => dimmerRenderer.set('opacity', v)
}),
tween({
onUpdate: (v) => modalRenderer.set({
opacity: 1 - v,
scaleX: toScaleXOut(v),
scaleY: toScaleYOut(v),
y: - 300 * easing.easeIn(v)
}),
duration: 200,
onComplete: closeComplete
})
])
]).start();
}
document.addEventListener('click', openModal);
document.addEventListener('click', cancelModal);
document.querySelector('.submit').addEventListener('click', submitModal);
View Compiled
This Pen doesn't use any external CSS resources.