interactive 16 grid illustrations :)
A Pen by Shunya Koide on CodePen.
interactive 16 grid illustrations :)
A Pen by Shunya Koide on CodePen.
<div id="print_container"> | |
<div id="grid_container"> | |
<!-- Following Eye --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<defs> | |
<clipPath id="clip-mask"> | |
<path fill="none" d="M95.86 50S75.33 79.47 50 79.47 4.14 50 4.14 50 24.67 20.53 50 20.53 95.86 50 95.86 50Z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-blue)" d="M0 0h100v100H0z" /> | |
<g class="eye"> | |
<path fill="var(--color-white)" d="M95.86 50S75.33 79.47 50 79.47 4.14 50 4.14 50 24.67 20.53 50 20.53 95.86 50 95.86 50Z" /> | |
<g clip-path="url(#clip-mask)"> | |
<circle class="eye-pupil" cx="50" cy="50" r="20.91" fill="var(--color-black)" /> | |
</g> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- ROTATING STARS --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-blue)" d="M0 0h100v100H0z" /> | |
<path class="star" d="M42.74 25.48c-10.29 0-18.64-8.34-18.64-18.64 0 10.29-8.34 18.64-18.64 18.64 10.29 0 18.64 8.34 18.64 18.64 0-10.29 8.34-18.64 18.64-18.64Z" fill="var(--color-white)" /> | |
<path class="star" d="M94.54 25.48c-10.29 0-18.64-8.34-18.64-18.64 0 10.29-8.34 18.64-18.64 18.64 10.29 0 18.64 8.34 18.64 18.64 0-10.29 8.34-18.64 18.64-18.64Z" fill="var(--color-white)" /> | |
<path class="star" d="M42.74 74.52c-10.29 0-18.64-8.34-18.64-18.64 0 10.29-8.34 18.64-18.64 18.64 10.29 0 18.64 8.34 18.64 18.64 0-10.29 8.34-18.64 18.64-18.64Z" fill="var(--color-white)" /> | |
<path class="star" d="M94.54 74.52c-10.29 0-18.64-8.34-18.64-18.64 0 10.29-8.34 18.64-18.64 18.64 10.29 0 18.64 8.34 18.64 18.64 0-10.29 8.34-18.64 18.64-18.64Z" fill="var(--color-white)" /> | |
</svg> | |
</div> | |
</div> | |
<!-- MORPHING BOXES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 200"> | |
<defs> | |
<clipPath id="a"> | |
<path fill="none" d="M0 0h100v100H0z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-blue)" d="M0 0h100v100H0z" /> | |
<g clip-path="url(#a)"> | |
<g id="circles"> | |
<g class="circle"> | |
<circle cx="50" cy="50" r="50" fill="var(--color-white)" /> | |
<path d="M100 50c0 27.61-22.39 50-50 50V0c27.61 0 50 22.39 50 50Z" fill="var(--color-black)" /> | |
</g> | |
<g class="circle"> | |
<circle cx="50" cy="250" r="50" fill="var(--color-white)" /> | |
<path d="M100 250c0 27.61-22.39 50-50 50V200c27.61 0 50 22.39 50 50Z" fill="var(--color-black)" /> | |
</g> | |
<g class="circle"> | |
<circle cx="50" cy="150" r="50" fill="var(--color-white)" /> | |
<path d="M0 150c0-27.61 22.39-50 50-50v100c-27.61 0-50-22.39-50-50Z" fill="var(--color-black)" /> | |
</g> | |
</g> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- HALF CIRCLES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg id="morph-boxes" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-blue)" d="M0 0h100v100H0z" /> | |
<path id="morph-box-1" class="morph-box" d="M28.88 8.4H9.87v19.01l19.01 19.01h19.01V27.41L28.88 8.4z" fill="var(--color-black)" /> | |
<path id="box-top-1" d="M9.87 8.4h19.01v19.01H9.87z" fill="var(--color-white)" /> | |
<path id="morph-box-2" class="morph-box" d="M73.18 8.4H54.17v19.01l19.01 19.01h19.01V27.41L73.18 8.4z" fill="var(--color-black)" /> | |
<path id="box-top-2" d="M54.17 8.4h19.01v19.01H54.17z" fill="var(--color-white)" /> | |
<path id="morph-box-3" class="morph-box" d="M28.88 53.58H9.87v19.01L28.88 91.6h19.01V72.59L28.88 53.58z" fill="var(--color-black)" /> | |
<path id="box-top-3" d="M9.87 53.58h19.01v19.01H9.87z" fill="var(--color-white)" /> | |
<path id="morph-box-4" class="morph-box" d="M73.18 53.58H54.17v19.01L73.18 91.6h19.01V72.59L73.18 53.58z" fill="var(--color-black)" /> | |
<path id="box-top-4" d="M54.17 53.58h19.01v19.01H54.17z" fill="var(--color-white)" /> | |
<g class="morph-shapes"> | |
<polygon id="morph-shape-4" points="92.19 72.59 73.18 72.59 73.18 91.6 73.18 91.6 92.19 91.6 92.19 72.59 92.19 72.59" fill="none" /> | |
<polygon id="morph-shape-2" points="92.19 27.41 73.18 27.41 73.18 46.42 73.18 46.42 92.19 46.42 92.19 27.41 92.19 27.41" fill="none" /> | |
<polygon id="morph-shape-1" points="47.89 27.41 28.88 27.41 28.88 46.42 28.88 46.42 47.89 46.42 47.89 27.41 47.89 27.41" fill="none" /> | |
<polygon id="morph-shape-3" points="47.89 72.59 28.88 72.59 28.88 91.6 28.88 91.6 47.89 91.6 47.89 72.59 47.89 72.59" fill="none" /> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- FOLLOWING STARS --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<defs> | |
<clipPath id="half-circle-clip-path"> | |
<path fill="none" d="M0 0h100v100H0z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-yellow)" d="M0 0h100v100H0z" /> | |
<g clip-path="url(#half-circle-clip-path)"> | |
<g id="half-circles"> | |
<path id="half-circle-1" class="half-circle" d="M0 50C0 22.39 22.39 0 50 0v100C22.39 100 0 77.61 0 50Z" fill="var(--color-blue)" /> | |
<path id="half-circle-2" class="half-circle" d="M50 50c0-27.61 22.39-50 50-50v100c-27.61 0-50-22.39-50-50Z" fill="var(--color-blue)" /> | |
<path id="half-circle-3" class="half-circle" d="M100 50c0-27.61 22.39-50 50-50v100c-27.61 0-50-22.39-50-50Z" fill="var(--color-blue)" /> | |
<path id="half-circle-4" class="half-circle" d="M150 50c0-27.61 22.39-50 50-50v100c-27.61 0-50-22.39-50-50Z" fill="var(--color-blue)" /> | |
</g> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- MORPHING HEART --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg id="following-stars" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-red)" d="M0 0h100v100H0z" /> | |
<path class="rotating-star" fill="var(--color-white)" d="M100 50C72.39 50 50 27.61 50 0c0 27.61-22.39 50-50 50 27.61 0 50 22.39 50 50 0-27.61 22.39-50 50-50Z" /> | |
<path class="following-star" fill="var(--color-yellow)" d="M100 50C72.39 50 50 27.61 50 0c0 27.61-22.39 50-50 50 27.61 0 50 22.39 50 50 0-27.61 22.39-50 50-50Z" /> | |
<path class="following-star" fill="var(--color-yellow)" d="M100 50C72.39 50 50 27.61 50 0c0 27.61-22.39 50-50 50 27.61 0 50 22.39 50 50 0-27.61 22.39-50 50-50Z" /> | |
<path class="following-star" fill="var(--color-yellow)" d="M100 50C72.39 50 50 27.61 50 0c0 27.61-22.39 50-50 50 27.61 0 50 22.39 50 50 0-27.61 22.39-50 50-50Z" /> | |
<path class="following-star" fill="var(--color-yellow)" d="M100 50C72.39 50 50 27.61 50 0c0 27.61-22.39 50-50 50 27.61 0 50 22.39 50 50 0-27.61 22.39-50 50-50Z" /> | |
</svg> | |
</div> | |
</div> | |
<!-- DRAW SVG LINES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-red)" d="M0 0h100v100H0z" /> | |
<path id="morph-heart" fill="var(--color-white)" d="M50 15.05c-10.76-10.76-28.22-10.76-38.98 0C.25 25.82.25 43.27 11.02 54.04L50 93.02l38.98-38.98c10.76-10.76 10.76-28.22 0-38.98C78.22 4.3 60.76 4.3 50 15.06Z" /> | |
<path id="morph-lip" fill="none" d="M89.74 42.61c-7-7.47-15.48-21.85-28.55-21.85-7.61 0-8.85 6.26-11.18 6.26s-3.58-6.26-11.18-6.26c-13.07 0-21.55 14.38-28.55 21.85-2.98 3.18-7.67 6.22-7.67 6.22s3.22 2.02 5.78 4.61c6.88 6.98 21.46 25.41 41.62 25.8 20.16-.39 34.75-18.82 41.62-25.8 2.56-2.6 5.78-4.61 5.78-4.61s-4.69-3.04-7.67-6.22Z" /> | |
</svg> | |
</div> | |
</div> | |
<!-- STRIPES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-yellow)" d="M0 0h100v100H0z" /> | |
<path class="square-stroke" d="M21.25 0v50c0 15.88 12.87 28.75 28.75 28.75S78.75 65.88 78.75 50" fill="none" stroke="var(--color-blue)" stroke-miterlimit="10" stroke-width="10" /> | |
<path class="square-stroke-right" d="M21.25 49.92c0-15.88 12.87-28.75 28.75-28.75s28.75 12.87 28.75 28.75V100" fill="none" stroke="var(--color-blue)" stroke-miterlimit="10" stroke-width="10" /> | |
<path class="square-stroke" d="M28.88 0v50c0 11.66 9.46 21.12 21.12 21.12S71.12 61.66 71.12 50" fill="none" stroke="var(--color-white)" stroke-miterlimit="10" stroke-width="10" /> | |
<path class="square-stroke-right" d="M28.88 49.92c0-11.66 9.46-21.12 21.12-21.12s21.12 9.46 21.12 21.12V100" fill="none" stroke="var(--color-white)" stroke-miterlimit="10" stroke-width="10" /> | |
<path class="square-stroke" d="M36.51 0v50c0 7.45 6.04 13.49 13.49 13.49S63.49 57.45 63.49 50" fill="none" stroke="var(--color-red)" stroke-miterlimit="10" stroke-width="10" /> | |
<path class="square-stroke-right" d="M36.51 49.92c0-7.45 6.04-13.49 13.49-13.49s13.49 6.04 13.49 13.49V100" fill="none" stroke="var(--color-red)" stroke-miterlimit="10" stroke-width="10" /> | |
</svg> | |
</div> | |
</div> | |
<!-- RANDOM CIRCLES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path d="M0 0h100v100H0z" fill="var(--color-yellow)" /> | |
<circle class="random-circle" cx="50" cy="50" r="50" fill="var(--color-white)" /> | |
<circle class="random-circle" cx="50" cy="50" r="40" fill="var(--color-blue)" /> | |
<circle class="random-circle" cx="50" cy="50" r="30" fill="var(--color-red)" /> | |
</svg> | |
</div> | |
</div> | |
<!-- STREACH BARS --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-white)" d="M0 0h100v100H0z" /> | |
<g class="bar"> | |
<path id="bar-1" d="M71.97 6.27c-6.07 0-10.98 4.92-10.98 10.98v52.27c0 6.07 4.92 10.98 10.98 10.98s10.98-4.92 10.98-10.98V17.25c0-6.07-4.92-10.98-10.98-10.98Z" fill="var(--color-red)" /> | |
<circle id="bar-1-circle-1" cx="71.97" cy="17.25" r="10.98" fill="var(--color-blue)" /> | |
<circle cx="71.97" cy="69.52" r="10.98" fill="var(--color-yellow)" /> | |
<path id="morph-bar-1" d="M71.97 48.56c-6.07 0-10.98 4.92-10.98 10.98v9.98c0 6.07 4.92 10.98 10.98 10.98s10.98-4.92 10.98-10.98v-9.98c0-6.07-4.92-10.98-10.98-10.98Z" fill="none" /> | |
</g> | |
<g class="bar"> | |
<path id="bar-2" d="M50 22.42c-6.07 0-10.98 4.92-10.98 10.98v52.27c0 6.07 4.92 10.98 10.98 10.98s10.98-4.92 10.98-10.98V33.4c0-6.07-4.92-10.98-10.98-10.98Z" fill="var(--color-blue)" /> | |
<circle cx="50" cy="33.41" r="10.98" fill="var(--color-yellow)" /> | |
<circle id="bar-2-circle-2" cx="50" cy="85.68" r="10.98" fill="var(--color-black)" /> | |
<path id="morph-bar-2" d="M50.06 22.42c-6.07 0-10.98 4.92-10.98 10.98v9.98c0 6.07 4.92 10.98 10.98 10.98s10.98-4.92 10.98-10.98V33.4c0-6.07-4.92-10.98-10.98-10.98Z" fill="none" /> | |
</g> | |
<g class="bar"> | |
<path id="bar-3" d="M28.15 7.27c-6.07 0-10.98 4.92-10.98 10.98v52.27c0 6.07 4.92 10.98 10.98 10.98s10.98-4.92 10.98-10.98V18.26c0-6.07-4.92-10.98-10.98-10.98Z" fill="var(--color-red)" /> | |
<circle id="bar-3-circle-1" cx="28.15" cy="18.26" r="10.98" fill="var(--color-black)" /> | |
<circle cx="28.15" cy="70.53" r="10.98" fill="var(--color-blue)" /> | |
<path id="morph-bar-3" fill="none" d="M27.86 37.18c-6.06.09-10.81 5.31-10.81 11.38v22.01c0 6.07 4.74 11.28 10.81 11.38 6.15.1 11.16-4.86 11.16-10.98v-22.8c0-6.12-5.01-11.08-11.16-10.98Z" /> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- ROTATING DISK--> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-white)" d="M0 0h100v100H0z" /> | |
<g id="disk"> | |
<path fill="none" d="M0 0h100v100H0z" /> | |
<path fill="var(--color-blue)" d="M100 50c0 27.61-22.39 50-50 50S0 77.61 0 50h100Z" /> | |
</g> | |
<circle cx="50" cy="50" r="25" fill="var(--color-black)" /> | |
</svg> | |
</div> | |
</div> | |
<!-- ARROWS --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 200"> | |
<defs> | |
<clipPath id="a"> | |
<path fill="none" d="M0 0h100v100H0z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-white)" d="M0 0h100v100H0z" /> | |
<g clip-path="url(#a)"> | |
<path id="arrow-1" d="M50 0 0 50h100L50 0z" fill="var(--color-red)" /> | |
<path id="arrow-2" d="M50 50 0 100h100L50 50z" fill="var(--color-black)" /> | |
<path id="arrow-3" d="M50 100 0 150h100l-50-50z" fill="var(--color-red)" /> | |
<path id="arrow-4" d="M50 150 0 200h100l-50-50z" fill="var(--color-black)" /> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- LINE DRAWING --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg id="lines" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<rect fill="var(--color-white)" width="100" height="100" /> | |
<path class="line" fill="none" stroke="var(--color-blue)" stroke-miterlimit="10" stroke-width="1" stroke-linecap="round" d="M50 9.95v80.32" /> | |
</svg> | |
</div> | |
</div> | |
<!-- STACK ELIPSES --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg id="ellipse" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<path fill="var(--color-yellow)" d="M0 0h100v100H0z" /> | |
<ellipse class="ellipse" cx="50" cy="25" fill="var(--color-white)" rx="50" ry="25" /> | |
</svg> | |
</div> | |
</div> | |
<!-- BALLANCING BALLS --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg id="balancing-balls" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<defs> | |
<clipPath id="ball-mask"> | |
<path fill="none" d="M0 0h100v100H0z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-red)" d="M0 0h100v100H0z" /> | |
<g clip-path="url(#ball-mask)"> | |
<circle class="ball" id="balance-ball-1" cx="50" cy="87" r="13" fill="var(--color-white)" /> | |
<circle class="ball" id="balance-ball-2" cx="50" cy="54" r="19" fill="var(--color-black)" /> | |
<circle class="ball" id="balance-ball-3" cx="50" cy="0" r="35" fill="var(--color-yellow)" /> | |
</g> | |
</svg> | |
</div> | |
</div> | |
<!-- Following Eye --> | |
<div class="box"> | |
<div class="box-content"> | |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> | |
<defs> | |
<clipPath id="clip-mask"> | |
<path fill="none" d="M95.86 50S75.33 79.47 50 79.47 4.14 50 4.14 50 24.67 20.53 50 20.53 95.86 50 95.86 50Z" /> | |
</clipPath> | |
</defs> | |
<path fill="var(--color-red)" d="M0 0h100v100H0z" /> | |
<g class="eye"> | |
<path fill="var(--color-white)" d="M95.86 50S75.33 79.47 50 79.47 4.14 50 4.14 50 24.67 20.53 50 20.53 95.86 50 95.86 50Z" /> | |
<g clip-path="url(#clip-mask)"> | |
<circle class="eye-pupil" cx="50" cy="50" r="20.91" fill="var(--color-black)" /> | |
</g> | |
</g> | |
</svg> | |
</div> | |
</div> | |
</div> | |
<div> | |
<div class="title_container"> | |
<!-- SPLIT TEXT --> | |
<p class="title">GSAP GRID</p> | |
<div class="color_pallet"> | |
<div class="blue"></div> | |
<div class="red"></div> | |
<div class="yellow"></div> | |
<div class="black"></div> | |
</div> | |
</div> | |
</div> | |
</div> |
// Drag and Move mouse around | |
// Hope you like it :) | |
// @shunyadezian | |
console.clear(); | |
gsap.registerPlugin( | |
MorphSVGPlugin, | |
DrawSVGPlugin, | |
SplitText, | |
Draggable, | |
InertiaPlugin | |
); | |
const container = document.getElementById("grid_container"); | |
// REVEAL GRID ITEM | |
const boxes = gsap.utils.toArray(".box"); | |
gsap.to(boxes, { | |
autoAlpha: 1, | |
duration: 0.8, | |
stagger: 0.1, | |
ease: "power1.inOut" | |
}); | |
// Following Eye | |
// ref: https://gsap.com/community/forums/topic/17962-cursor-follows-the-eyes/ | |
container.addEventListener("mousemove", (e) => { | |
const { clientX, clientY } = e; | |
const maxTrans = 20; | |
const maxX = container.clientWidth / 2; | |
const maxY = container.clientHeight / 2; | |
const eye = document.querySelectorAll(".eye"); | |
const pupil = document.querySelectorAll(".eye-pupil"); | |
for (let i = 0; i < eye.length; i++) { | |
const eyeRect = eye[i].getBoundingClientRect(); | |
const r = eyeRect.width / 2; | |
const centerX = eyeRect.left + r; | |
const centerY = eyeRect.top + r; | |
const x = clientX - centerX; | |
const y = clientY - centerY; | |
const xPercent = x / maxX; | |
const yPercent = y / maxY; | |
const scaledXPercent = xPercent * maxTrans; | |
const scaledYPercent = yPercent * maxTrans; | |
gsap.to(pupil[i], { | |
xPercent: scaledXPercent, | |
yPercent: scaledYPercent, | |
duration: 0.2, | |
overwrite: "auto" | |
}); | |
gsap.to(eye[i], { | |
xPercent: scaledXPercent * 0.4, | |
yPercent: scaledYPercent * 0.4, | |
duration: 0.2, | |
overwrite: "auto" | |
}); | |
} | |
}); | |
// ROTATING STARS | |
const starTL = gsap.timeline({ repeat: -1, repeatDelay: 0.5 }); | |
starTL | |
.to(".star", { | |
rotate: 360, | |
transformOrigin: "50% 50%", | |
duration: 1, | |
stagger: 0.5 | |
}) | |
.to(".star", { | |
scale: 1.5, | |
transformOrigin: "50% 50%", | |
duration: 0.2, | |
ease: "power1.out", | |
repeat: 1, | |
yoyo: true | |
}); | |
// ROTATING CIRCLES | |
gsap.to("#circles", { | |
y: -200, | |
duration: 1.5, | |
ease: "none", | |
repeat: -1 | |
}); | |
gsap.to(".circle", { | |
rotate: 360, | |
transformOrigin: "50% 50%", | |
duration: 3, | |
repeat: -1, | |
ease: "none" | |
}); | |
// MORPHING BOXES | |
const morphBoxes = gsap.utils.toArray(".morph-box"); | |
const morphDuration = 1; | |
morphBoxes.forEach((box, i) => { | |
gsap.to(box, { | |
morphSVG: `#morph-shape-${i + 1}`, | |
duration: morphDuration, | |
ease: "power1.inOut", | |
repeat: -1, | |
yoyo: true, | |
delay: i * 0.1 | |
}); | |
gsap.to(`#box-top-${i + 1}`, { | |
y: 19, | |
x: 19, | |
duration: morphDuration, | |
ease: "power1.inOut", | |
repeat: -1, | |
yoyo: true, | |
delay: i * 0.1 | |
}); | |
}); | |
// HALF CIRCLES | |
const halfCircleTl = gsap.timeline({ repeat: -1 }).timeScale(0.5); | |
const halfCircleEase = "power2.inOut"; | |
const halfCirclesScale = { | |
scale: 0, | |
transformOrigin: "0% 50%", | |
ease: halfCircleEase | |
}; | |
halfCircleTl | |
.to("#half-circle-1", { | |
...halfCirclesScale | |
}) | |
.to( | |
"#half-circle-2", | |
{ | |
x: -50, | |
ease: halfCircleEase | |
}, | |
"<" | |
) | |
.to( | |
"#half-circle-3", | |
{ | |
x: -50, | |
transformOrigin: "0% 50%", | |
ease: halfCircleEase | |
}, | |
"<" | |
) | |
.to( | |
"#half-circle-4", | |
{ | |
x: -50, | |
ease: halfCircleEase | |
}, | |
"<" | |
) | |
.to("#half-circle-2", { | |
...halfCirclesScale | |
}) | |
.to( | |
"#half-circle-3", | |
{ | |
x: -100, | |
ease: halfCircleEase | |
}, | |
"<" | |
) | |
.to( | |
"#half-circle-4", | |
{ | |
x: -100, | |
ease: halfCircleEase | |
}, | |
"<" | |
); | |
// FOLLOWING STARS | |
// ref: https://gsap.com/community/forums/topic/30502-mouse-cursor-follow-animation/ | |
const starContainer = document.querySelector("#following-stars"); | |
gsap.set(".following-star", { | |
xPercent: -50, | |
yPercent: -50, | |
x: 0, | |
y: 100, | |
transformOrigin: "center", | |
scale: 0 | |
}); | |
let initialMouseMove = true; | |
let timer; | |
starContainer.addEventListener("mousemove", (e) => { | |
const { clientX, clientY } = e; | |
const containerRect = starContainer.getBoundingClientRect(); | |
const centerX = containerRect.left; | |
const centerY = containerRect.top; | |
const x = clientX - centerX; | |
const y = clientY - centerY; | |
// if it's the first mouse movement run this | |
if (initialMouseMove) { | |
initialMouseMove = false; | |
gsap.to(".following-star", { | |
scale: 0.4, | |
stagger: 0.02, | |
ease: "sine.out" | |
}); | |
} | |
const mouseStopped = () => { | |
// reset this variable | |
// so we can track the first mouse move again | |
initialMouseMove = true; | |
gsap.to(".following-star", { | |
scale: 0, | |
stagger: 0.02, | |
ease: "sine.inOut" | |
}); | |
}; | |
// // clear the timer every time the mouse moves | |
clearTimeout(timer); | |
// // set a timer for 0.2 second | |
timer = setTimeout(mouseStopped, 20); | |
gsap.to(".following-star", { | |
duration: 0.5, | |
overwrite: "auto", | |
x: x / 2, | |
y: y / 2, | |
stagger: 0.1, | |
ease: "none" | |
}); | |
}); | |
// MORPHING HEART | |
gsap.to("#morph-heart", { | |
morphSVG: `#morph-lip`, | |
duration: 2, | |
ease: "power3.inOut", | |
repeat: -1, | |
yoyo: true | |
}); | |
// STRIPES | |
gsap.set(".square-stroke", { | |
drawSVG: 0 | |
}); | |
gsap.to(".square-stroke", { | |
drawSVG: '100% "100%"', | |
duration: 2, | |
ease: "power3.out", | |
repeat: -1, | |
yoyo: true, | |
stagger: 0.1 | |
}); | |
gsap.set(".square-stroke-right", { | |
drawSVG: "100% 100%" | |
}); | |
gsap.to(".square-stroke-right", { | |
drawSVG: "0% 100%", // reverse | |
duration: 2, | |
ease: "power3.out", | |
repeat: -1, | |
yoyo: true, | |
stagger: 0.1 | |
}); | |
// RANDOM CIRCLES | |
const randomCircleTl = gsap.timeline({ repeat: -1, repeatRefresh: true }); | |
randomCircleTl | |
.to(".random-circle", { | |
x: () => gsap.utils.random(-40, 40, 5), | |
y: () => gsap.utils.random(-40, 40, 5), | |
scale: 0, | |
transformOrigin: "center" | |
}) | |
.to(".random-circle", { | |
scale: () => gsap.utils.random(0.7, 1, 0.1), | |
duration: 1, | |
ease: "power3.inOut", | |
stagger: 0.2 | |
}); | |
// STREACH BARS | |
gsap.to(".bar", { | |
y: -5, | |
duration: 1, | |
ease: "power1.inOut", | |
repeat: -1, | |
yoyo: true, | |
stagger: 1 | |
}); | |
gsap.to("#bar-1", { | |
morphSVG: "#morph-bar-1", | |
duration: 1, | |
ease: "power3.inOut", | |
repeat: -1, | |
yoyo: true | |
}); | |
gsap.to("#bar-1-circle-1", { | |
y: 40, | |
duration: 1, | |
ease: "power3.inOut", | |
repeat: -1, | |
yoyo: true | |
}); | |
gsap.to("#bar-2", { | |
morphSVG: "#morph-bar-2", | |
duration: 3, | |
ease: "sine.inOut", | |
repeat: -1, | |
yoyo: true | |
}); | |
gsap.to("#bar-2-circle-2", { | |
y: -40, | |
duration: 3, | |
ease: "sine.inOut", | |
repeat: -1, | |
yoyo: true | |
}); | |
gsap.to("#bar-3", { | |
morphSVG: "#morph-bar-3", | |
duration: 2, | |
ease: "circ", | |
repeat: -1, | |
yoyo: true | |
}); | |
gsap.to("#bar-3-circle-1", { | |
y: 30, | |
duration: 2, | |
ease: "circ", | |
repeat: -1, | |
yoyo: true | |
}); | |
// ROTATING DISK | |
gsap.to("#disk", { | |
rotate: 360, | |
transformOrigin: "50% 50%", | |
duration: 3, | |
ease: "elastic.out(1,0.5)", | |
repeat: -1, | |
yoyo: true | |
}); | |
// ARROWS | |
const arrowTL = gsap.timeline({ repeat: -1 }); | |
arrowTL | |
.to("#arrow-1", { | |
scale: 0, | |
transformOrigin: "top center", | |
duration: 1, | |
ease: "power3.inOut" | |
}) | |
.to( | |
"#arrow-2", | |
{ | |
y: -50, | |
duration: 1, | |
ease: "power3.inOut" | |
}, | |
"<" | |
) | |
.to("#arrow-3", { | |
y: -50, | |
duration: 1, | |
ease: "power3.inOut" | |
}) | |
.to("#arrow-2", { | |
scale: 0, | |
transformOrigin: "top center", | |
duration: 1, | |
ease: "power3.inOut" | |
}) | |
.to( | |
"#arrow-3", | |
{ | |
y: -100, | |
duration: 1, | |
ease: "power3.inOut" | |
}, | |
"<" | |
) | |
.to("#arrow-4", { | |
y: -100, | |
duration: 1, | |
ease: "power3.inOut" | |
}); | |
// LINE DRAWING | |
const line = document.querySelector("#lines"); | |
const lineOrigin = document.querySelector("#lines path"); | |
// Number of duplicates | |
const numOfLines = 20; | |
for (let i = 0; i < numOfLines; i++) { | |
const clonedPath = lineOrigin.cloneNode(true); | |
clonedPath.setAttribute("class", "line"); | |
line.appendChild(clonedPath); | |
} | |
const lines = gsap.utils.toArray(".line"); | |
lines.forEach((line, i) => { | |
gsap.set(line, { | |
drawSVG: "100% 0%", | |
rotate: (i * 180) / numOfLines, | |
transformOrigin: "center" | |
}); | |
}); | |
gsap.to(lines, { | |
rotate: "+=360", | |
ease: "power3.inOut", | |
repeat: -1, | |
stagger: 0.1, | |
duration: 4 | |
}); | |
gsap.to("#lines path", { | |
drawSVG: "100% 50%", | |
duration: 2, | |
ease: "power3.inOut", | |
repeat: -1, | |
yoyo: true, | |
stagger: 0.1 | |
}); | |
// STACK ELIPSES | |
const ellipse = document.querySelector("#ellipse"); | |
const ellipsOrigin = document.querySelector("#ellipse ellipse"); | |
// Number of duplicates | |
const numOfEllipses = 10; | |
for (let i = 0; i < numOfEllipses; i++) { | |
const clonedPath = ellipsOrigin.cloneNode(true); | |
ellipse.appendChild(clonedPath); | |
} | |
const ellipses = gsap.utils.toArray(".ellipse"); | |
ellipses.forEach((ellipse, i) => { | |
gsap.set(ellipse, { | |
transformOrigin: "bottom center" | |
}); | |
}); | |
gsap.to(ellipses, { | |
y: 50, | |
fill: "#9d2719", | |
ease: "power3.inOut", | |
stagger: 0.1, | |
repeat: -1, | |
duration: 1, | |
yoyo: true | |
}); | |
// BALLANCING BALLS | |
const ballContainer = document.querySelector("#balancing-balls"); | |
gsap.set("#balance-ball-1", { | |
xPercent: -190, | |
transformOrigin: "center", | |
x: 50 | |
}); | |
gsap.set("#balance-ball-2", { | |
xPercent: -130, | |
transformOrigin: "center", | |
x: 50 | |
}); | |
gsap.set("#balance-ball-3", { | |
xPercent: -70, | |
transformOrigin: "center", | |
x: 50 | |
}); | |
ballContainer.addEventListener("mousemove", (e) => { | |
const { clientX } = e; | |
const containerRect = ballContainer.getBoundingClientRect(); | |
const centerX = containerRect.left; | |
const x = clientX - centerX; | |
gsap.to("#balance-ball-1", { | |
duration: 0.5, | |
overwrite: "auto", | |
x: x / 2, | |
ease: "power4.out" | |
}); | |
gsap.to("#balance-ball-2", { | |
duration: 0.5, | |
overwrite: "auto", | |
x: x / 2, | |
ease: "power4.out", | |
delay: 0.02 | |
}); | |
gsap.to("#balance-ball-3", { | |
duration: 0.5, | |
overwrite: "auto", | |
x: x / 2, | |
ease: "power4.out", | |
delay: 0.045 | |
}); | |
}); | |
ballContainer.addEventListener("mouseleave", () => { | |
gsap.to(".ball", { | |
x: 50, | |
stagger: 0.05, | |
ease: "sine.inOut" | |
}); | |
}); | |
// SPLIT TEXT | |
const title = document.querySelector(".title"); | |
const split = new SplitText(title, { type: "chars" }); | |
gsap.from(split.chars, { | |
duration: 0.5, | |
y: 100, | |
stagger: 0.1, | |
ease: "back.out" | |
}); | |
const titleSpinTL = gsap.timeline({ paused: true }); | |
titleSpinTL.to(split.chars, { | |
duration: 0.5, | |
rotateY: 360, | |
stagger: 0.1, | |
repeat: 1, | |
yoyo: true | |
}); | |
title.addEventListener("mouseenter", () => { | |
titleSpinTL.restart(); | |
}); | |
//////////////////////////////////// | |
// GRID SYSTEM | |
//////////////////////////////////// | |
//ref: https://codepen.io/osublake/pen/NrRJwm?editors=0110 | |
//ref: https://codepen.io/GreenSock/pen/JjwZzNG?editors=0010 | |
const colSize = 200; | |
const rowSize = 200; | |
const gridRows = 4; | |
const gridColumns = 4; | |
function createGrid(gridRows, gridColumns) { | |
for (let i = 0; i < gridRows * gridColumns; i++) { | |
const y = Math.floor(i / gridColumns) * rowSize; | |
const x = (i * colSize) % (gridColumns * colSize); | |
const cell = document.createElement("div"); | |
cell.classList.add("cell"); | |
cell.style.width = colSize - 1 + "px"; | |
cell.style.height = rowSize - 1 + "px"; | |
cell.style.top = y + "px"; | |
cell.style.left = x + "px"; | |
container.appendChild(cell); | |
} | |
} | |
createGrid(gridRows, gridColumns); | |
let clampCol = gsap.utils.clamp(0, gridColumns - 1); | |
let clampRow = gsap.utils.clamp(0, gridRows - 1); | |
let cells = []; | |
// Map cell locations to array | |
for (let row = 0; row < gridRows; row++) { | |
for (let col = 0; col < gridColumns; col++) { | |
cells.push({ | |
row: row, | |
col: col, | |
x: col * colSize, | |
y: row * rowSize | |
}); | |
} | |
} | |
let listItems = gsap.utils.toArray(".box").sort(() => 0.5 - Math.random()); // Randomize list items | |
let sortables = listItems.map(Sortable); // Array of sortables | |
gsap.to(container, { autoAlpha: 1, duration: 0.5 }); | |
function changeIndex(item, to, sameRow, sameCol) { | |
// Check if adjacent to new position | |
if ((sameRow && !sameCol) || (!sameRow && sameCol)) { | |
// Swap positions in array | |
var temp = sortables[to]; | |
sortables[to] = item; | |
sortables[item.index] = temp; | |
} else { | |
// Change position in array | |
arrayMove(sortables, item.index, to); | |
} | |
// Simple, but not optimized way to change element's position in DOM. Not always necessary. | |
sortables.forEach((sortable) => container.appendChild(sortable.element)); | |
// Set index for each sortable | |
sortables.forEach((sortable, index) => sortable.setIndex(index)); | |
} | |
function Sortable(element, index) { | |
let content = element.querySelector(".box-content"); | |
let animation = gsap.to(content, { | |
duration: 0.3, | |
boxShadow: "rgba(0,0,0,0.8) 0px 16px 32px 0px", | |
force3D: true, | |
scale: 1.1, | |
paused: true | |
}); | |
let dragger = new Draggable(element, { | |
onDragStart: downAction, | |
onRelease: upAction, | |
onDrag: dragAction | |
}); | |
// let position = element._gsTransform; | |
let getProp = gsap.getProperty(element); | |
// Public properties and methods | |
let sortable = { | |
cell: cells[index], | |
dragger: dragger, | |
element: element, | |
index: index, | |
setIndex: setIndex | |
}; | |
gsap.set(element, { | |
x: sortable.cell.x, | |
y: sortable.cell.y | |
}); | |
function setIndex(index) { | |
let cell = cells[index]; | |
// var dirty = position.x !== cell.x || position.y !== cell.y; | |
let dirty = getProp("x") !== cell.x || getProp("y") !== cell.y; | |
sortable.cell = cell; | |
sortable.index = index; | |
// Don't layout if you're dragging | |
if (!dragger.isDragging && dirty) layout(); | |
} | |
function downAction() { | |
animation.play(); | |
this.update(); | |
} | |
function dragAction() { | |
let col = clampCol(Math.round(this.x / colSize)); | |
let row = clampRow(Math.round(this.y / rowSize)); | |
let cell = sortable.cell; | |
let sameCol = col === cell.col; | |
let sameRow = row === cell.row; | |
// Check if position has changed | |
if (!sameRow || !sameCol) { | |
// Calculate the new index | |
var index = gridColumns * row + col; | |
// Update the model | |
changeIndex(sortable, index, sameRow, sameCol); | |
} | |
} | |
function upAction() { | |
animation.reverse(); | |
layout(); | |
} | |
function layout() { | |
gsap.to(element, { | |
duration: 0.3, | |
x: sortable.cell.x, | |
y: sortable.cell.y | |
}); | |
} | |
return sortable; | |
} | |
// Changes an elements's position in array | |
function arrayMove(array, from, to) { | |
array.splice(to, 0, array.splice(from, 1)[0]); | |
} |
<script src="https://unpkg.com/gsap@3/dist/gsap.min.js"></script> | |
<script src="https://assets.codepen.io/16327/SplitText3.min.js"></script> | |
<script src="https://assets.codepen.io/16327/MorphSVGPlugin3.min.js"></script> | |
<script src="https://assets.codepen.io/16327/DrawSVGPlugin3.min.js"></script> | |
<script src="https://unpkg.com/gsap@3/dist/Draggable.min.js"></script> | |
<script src="https://assets.codepen.io/16327/InertiaPlugin.min.js"></script> |
@import url("https://fonts.googleapis.com/css2?family=Tilt+Warp&display=swap"); | |
:root { | |
--color-blue: #154084; | |
--color-red: #9d2719; | |
--color-yellow: #d7b418; | |
--color-white: #fff3e7; | |
--color-black: #222222; | |
--box-size: 200px; | |
font-family: "Tilt Warp", sans-serif; | |
color: var(--color-text); | |
background-color: var(--color-black); | |
} | |
body { | |
height: 100vh; | |
width: 100vw; | |
margin: 0; | |
padding: 0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
overflow: hidden; | |
} | |
#print_container { | |
padding: 12px; | |
background-color: var(--color-white); | |
display: flex; | |
flex-direction: column; | |
justify-content: space-between; | |
gap: 12px; | |
} | |
.title_container { | |
display: flex; | |
justify-content: space-between; | |
align-items: end; | |
} | |
.title { | |
font-size: 5rem; | |
line-height: 5rem; | |
font-weight: 600; | |
letter-spacing: -2px; | |
color: var(--color-black); | |
margin: 0; | |
} | |
.color_pallet { | |
display: flex; | |
} | |
.color_pallet div { | |
width: 20px; | |
height: 20px; | |
} | |
.color_pallet .blue { | |
background-color: var(--color-blue); | |
} | |
.color_pallet .red { | |
background-color: var(--color-red); | |
} | |
.color_pallet .yellow { | |
background-color: var(--color-yellow); | |
} | |
.color_pallet .black { | |
background-color: var(--color-black); | |
} | |
#grid_container { | |
position: relative; | |
width: calc(var(--box-size) * 4); | |
height: calc(var(--box-size) * 4); | |
} | |
.cell { | |
position: absolute; | |
pointer-events: none; | |
/* border: 1px solid #ffffff82; */ | |
} | |
.box { | |
opacity: 0; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: var(--box-size); | |
height: var(--box-size); | |
line-height: var(--box-size); | |
cursor: pointer; | |
overflow: hidden; | |
} | |
.box-content { | |
height: 100%; | |
background-color: rgb(255, 255, 255); | |
} |