<div id="app">
<div id="overlay">
<button class="replaybutton">Replay <svg class="replayicon" height="20" width="20" fill="#fff" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="m 8.0017778,1037.4886 0,1.7441 c -2.79721,0 -5.23232,1.9407 -5.85352,4.668 -0.6212,2.7274 0.73261,5.5328 3.25391,6.7442 2.5213,1.2113 5.5558192,0.5155 7.2968692,-1.6739 1.74105,-2.1893 1.73629,-5.3045 -0.0117,-7.4883 l -1.17187,0.9375 c 1.31573,1.6438 1.32022,3.9693 0.01,5.6172 -1.31049,1.6479 -3.5768192,2.1677 -5.4746092,1.2559 -1.89779,-0.9118 -2.90703,-3.0057 -2.43945,-5.0586 0.46757,-2.0529 2.28516,-3.502 4.39062,-3.502 l 0,1.7989 2.9999992,-2.5215 z" color="#000" font-weight="400" font-family="sans-serif" overflow="visible" transform="translate(0 -1036.362)"/>
<style>
</style>
</svg></button>
</div>
<div class="explainer">
<h2 class="explain1">We start with an object</h2>
<h2 class="explain2 visually-hidden">We add a handler and proxy the object</h2>
<h2 class="explain3 visually-hidden">In this case it's the same</h2>
<h2 class="explain4 visually-hidden">But look! We can intercept the object with this proxy</h2>
<h2 class="explain6 visually-hidden">But here's the cool part: if we change the first object...</h2>
<h2 class="explain7 visually-hidden">The proxied object updates accordingly, without adjusting the handler!</h2>
<h2 class="explain8 visually-hidden">With reactivity we can respond to changes instantly! 🎉</h2>
</div>
<main>
<section>
<p class="code code1">target: {<br>
<span class="string sp">width</span>: <span class="cube1prop1">200</span>,<br>
<span class="string sp">background</span>: 'white'<br>
}</p>
<div class="scene">
<div class="show-front cube cube1">
<div class="face front">front</div>
<div class="face back">back</div>
<div class="face right">right</div>
<div class="face left">left</div>
<div class="face top">top</div>
<div class="face bottom">bottom</div>
</div>
</div>
</section>
<section class="handler">
<p class="code code2"><span class="keyword">const</span> handler = {<br>
<span class="sp"></span><span class="keyword">get</span>(target, objectKey) {<br>
<span class="visually-hidden ifblock">
<span class="sp"></span><span class="sp"></span><span class="keyword">if</span> (objectKey === <span class="string">'background'</span>) {<br>
<span class="sp"></span><span class="sp"></span><span class="sp"></span><span class="keyword">return</span> <span class="string">'green'</span><br>
<span class="sp"></span><span class="sp"></span>}<br>
</span><!--visuallyhidden ifblock-->
<span class="sp"></span><span class="sp"></span><span class="keyword">return</span> target[objectKey]<br>
<span class="sp"></span>}<br>
}<br>
<br>
<span class="keyword">const</span> proxiedObj = <span class="keyword">new Proxy</span>(target, handler)
</p>
</section>
<section>
<p class="code code3">proxiedObj: {<br>
<span class="string sp">width</span>: <span class="cube2prop1">200</span>,<br>
<span class="string sp">background</span>: '<span class="cube2prop2">white</span>'<br>
}</p>
<div class="scene">
<div class="show-front cube cube2">
<div class="face front">front</div>
<div class="face back">back</div>
<div class="face right">right</div>
<div class="face left">left</div>
<div class="face top">top</div>
<div class="face bottom">bottom</div>
</div>
</div>
</section>
</main>
</div>
@function strip-unit($value) {
@return $value / ($value * 0 + 1);
}
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
$u1: unit($min-vw);
$u2: unit($max-vw);
$u3: unit($min-font-size);
$u4: unit($max-font-size);
@if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
& {
font-size: $min-font-size;
@media screen and (min-width: $min-vw) {
font-size: calc(#{$min-font-size} + #{strip-unit($max-font-size - $min-font-size)} * ((100vw - #{$min-vw}) / #{strip-unit($max-vw - $min-vw)}));
}
@media screen and (min-width: $max-vw) {
font-size: $max-font-size;
}
}
}
}
#app {
text-size-adjust: 100%;
text-size-adjust: 100%;
osx-font-smoothing: grayscale;
font-smoothing: antialiased;
}
main {
margin: 0 0 0 20px;
width: 96%;
height: 400px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 40px;
}
h2 {
color: #273849;
text-align: center;
margin-top: 40px;
@include fluid-type(300px, 1200px, 14px, 28px);
}
section {
padding: 0;
}
* { box-sizing: border-box; }
body {
font-family: 'Source Sans Pro', sans-serif;
color: #4f5959;
}
#overlay {
opacity: 0;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
z-index: 1000;
}
.replaybutton {
display: table;
border: none;
padding: 0.7rem 1.5rem 0.9rem;
margin: 100px auto;
border-radius: 4px;
text-decoration: none;
background: rgb(66, 185, 131);
color: #ffffff;
font-family: 'Source Sans Pro', sans-serif;
font-size: 1.2rem;
cursor: pointer;
text-align: center;
transition: background 250ms ease-in-out,
transform 150ms ease;
appearance: none;
appearance: none;
}
.replaybutton:hover,
.replaybutton:focus {
background: #36996c;
}
.replaybutton:focus {
outline: 1px solid #fff;
outline-offset: -4px;
}
button:active {
transform: scale(0.99);
}
.replayicon {
margin: 0 0 -4px 2px;
}
$bwidth: 200px;
$bheight: 100px;
.scene {
width: $bwidth;
height: $bwidth;
margin: 15px;
perspective: 400px;
}
.cube {
width: $bwidth;
height: $bwidth;
position: relative;
transform-style: preserve-3d;
transform: translateZ(-$bheight);
transition: transform 1s ease;
}
.cube.show-front { transform: translateZ(-$bheight) rotateY( 0deg); }
.cube.show-right { transform: translateZ(-$bheight) rotateY( -90deg); }
.cube.show-back { transform: translateZ(-$bheight) rotateY(-180deg); }
.cube.show-left { transform: translateZ(-$bheight) rotateY( 90deg); }
.cube.show-top { transform: translateZ(-$bheight) rotateX( -90deg); }
.cube.show-bottom { transform: translateZ(-$bheight) rotateX( 90deg); }
.face {
font-family: 'Roboto Mono', monospace;
position: absolute;
width: $bwidth;
height: $bwidth;
border: 2px dotted #4f5959;
line-height: 200px;
font-size: 40px;
font-weight: bold;
color: rgba(66, 185, 131, 0.68);
text-align: center;
background-color: rgba(255, 255, 255, 0.75);
}
.front { transform: rotateY( 0deg) translateZ($bheight); }
.right { transform: rotateY( 90deg) translateZ($bheight); }
.back { transform: rotateY(180deg) translateZ($bheight); }
.left { transform: rotateY(-90deg) translateZ($bheight); }
.top { transform: rotateX( 90deg) translateZ($bheight); }
.bottom { transform: rotateX(-90deg) translateZ($bheight); }
label { margin-right: 10px; }
.code, pre, code {
font-family: 'Roboto Mono', monospace;
@include fluid-type(500px, 1200px, 9px, 13px);
}
.handler {
margin-top: 70px;
padding: 0;
}
.string {
color: #42b983;
}
.keyword {
color: #d63200;
}
.sp {
margin-left: 20px;
}
.explainer {
height: 50px;
}
.explainer, .code1, .code2, .code3, .cube1, .cube2 {
visibility: hidden;
}
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
}
View Compiled
gsap.set(".explainer, .code1, .code2, .code3, .cube1, .cube2", {
visibility: "visible"
});
//------------------//
// main animation //
//------------------//
const code1 = new SplitText(".code1", { type: "chars" });
const code3 = new SplitText(".code3", { type: "chars" });
const explainText = new SplitText(".explainer", { type: "chars" });
gsap.set(".explainer h2 div", {
opacity: 0,
scaleX: 0
});
gsap.set(".code1 div, .code2, .code3 div", {
opacity: 0
});
gsap.set(".face", {
opacity: 0,
scale: 0.5,
transformOrigin: "50% 50%"
});
const mainElements = () => {
gsap
.timeline({
delay: 0.5
})
.to(".explain1 div", {
opacity: 1,
scaleX: 1,
duration: 0.4,
delay: 0.5,
stagger: 0.03,
ease: "sine"
})
.to(code1.chars, {
opacity: 1,
ease: "power4",
duration: 0.2,
stagger: 0.03
})
.to(".cube1 .face", {
opacity: 1,
ease: "sine",
duration: 0.5,
scale: 1,
stagger: -0.05
})
.to(".explain1 div", {
opacity: 0,
scaleX: 0,
duration: 0.1,
stagger: 0.02,
ease: "sine.in"
})
.call(hideShow, [".explain1", ".explain2"])
.to(".explain2 div", {
opacity: 1,
scaleX: 1,
duration: 0.4,
delay: 0.4,
stagger: 0.02,
ease: "sine"
})
.to(".code2", {
opacity: 1,
ease: "sine",
duration: 0.6
})
.to(".explain2 div", {
opacity: 0,
scaleX: 0,
delay: 1,
duration: 0.1,
stagger: 0.02,
ease: "sine.in"
})
.call(hideShow, [".explain2", ".explain3"])
.to(".explain3 div", {
opacity: 1,
scaleX: 1,
duration: 0.4,
delay: 1.5,
stagger: 0.02,
ease: "sine"
})
.to(code3.chars, {
opacity: 1,
ease: "power4",
duration: 0.2,
stagger: 0.03
})
.to(".cube2 .face", {
opacity: 1,
ease: "sine",
duration: 0.5,
scale: 1,
stagger: -0.05
})
.to(".explain3 div", {
opacity: 0,
scaleX: 0,
delay: 0.3,
duration: 0.1,
stagger: 0.02,
ease: "sine.in"
})
.call(hideShow, [".explain3", ".explain4"])
.to(".explain4 div", {
opacity: 1,
scaleX: 1,
delay: 1,
duration: 0.4,
stagger: 0.02,
ease: "sine"
})
.call(showElement, [".ifblock"])
// explainer
.to(".ifblock", {
background: "#eee",
ease: "sine",
duration: 0.4
})
.call(updateText, [".cube2prop2", "green"])
.add("updateP1")
.to(
".ifblock",
{
background: "none",
ease: "sine.easeIn",
duration: 0.3
},
"updateP1"
)
.to(".explain4 div", {
opacity: 0,
scaleX: 0,
delay: 1,
duration: 0.1,
stagger: 0.02,
ease: "sine.in"
})
.call(hideShow, [".explain4", ".explain6"])
.to(
".cube2 .face",
{
backgroundColor: "rgba(66, 185, 131, 0.68)",
border: "2px solid #4f5959",
ease: "sine",
duration: 1.5
},
"updateP1"
)
.to(
".cube2 .face",
{
color: "rgba(255, 255, 255, 1)",
ease: "sine",
duration: 1
},
"updateP1+=0.25"
);
};
//-------------------//
// final animation //
//-------------------//
const finalAnimation = () => {
gsap
.timeline({ delay: 18 })
.to(".explain6 div", {
opacity: 1,
scaleX: 1,
duration: 0.2,
delay: 1,
stagger: 0.01,
ease: "sine"
})
.call(updateText, [".cube1prop1", "250"])
.call(updateText, [".cube2prop1", "250"])
.add("finalstart")
.to(".cube1prop1, .cube2prop1", {
background: "#eee",
ease: "sine",
duration: 0.4
})
.to(".cube1prop1, .cube2prop1", {
background: "none",
ease: "sine.easeIn",
duration: 0.3
})
.to(
".front, .back, .top, .bottom",
{
width: 250,
ease: "sine",
duration: 0.3
},
"finalstart+=0.1"
)
.to(
".right",
{
x: 150,
ease: "sine",
duration: 0.3
},
"finalstart+=0.1"
)
.to(".explain6 div", {
opacity: 0,
scaleX: 0,
delay: 1,
duration: 0.1,
stagger: 0.01,
ease: "sine.in"
})
.call(hideShow, [".explain6", ".explain7"])
.to(".explain7 div", {
opacity: 1,
scaleX: 1,
duration: 0.2,
stagger: 0.01,
ease: "sine"
})
.to(".explain7 div", {
opacity: 0,
scaleX: 0,
delay: 3,
duration: 0.1,
stagger: 0.01,
ease: "sine.in"
})
.call(hideShow, [".explain7", ".explain8"])
.add("finalspin", "+=0.5")
.to(
".explain8 div",
{
opacity: 1,
scaleX: 1,
duration: 0.2,
stagger: 0.01,
ease: "sine"
},
"finalspin"
)
.to(
".cube",
{
rotationY: 360,
ease: "power4.in",
duration: 1.25
},
"finalspin"
)
.to("#overlay", {
delay: 3,
opacity: 1,
duration: 0.25
})
.to(".replayicon", {
rotation: 720,
duration: 1.5,
ease: "back"
});
};
window.onload = () => {
mainElements();
finalAnimation();
};
//-------------//
// helpers //
//-------------//
const hideShow = (el1, el2) => {
let elref1 = document.querySelector(el1);
elref1.classList.add("visually-hidden");
let elref2 = document.querySelector(el2);
elref2.classList.remove("visually-hidden");
};
const showElement = (el) => {
let elref = document.querySelector(el);
elref.classList.remove("visually-hidden");
};
const updateText = (el, text) => {
let elref = document.querySelector(el);
elref.innerHTML = text;
};
const button = document.querySelector(".replaybutton");
button.addEventListener("click", () => {
location.reload();
});
View Compiled
This Pen doesn't use any external CSS resources.