Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Created July 9, 2025 21:41
Show Gist options
  • Save aldoyh/ab868f483666a539edac9124b253310a to your computer and use it in GitHub Desktop.
Save aldoyh/ab868f483666a539edac9124b253310a to your computer and use it in GitHub Desktop.
Morphing Play/Pause (MorphSVG)
<!--------- Osmo [https://osmo.supply/] --------->
<button data-play-pause="toggle" class="play-pause-button">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 24 25" fill="none" class="play-pause-icon">
<path d="M3.5 5L3.50049 3.9468C3.50049 3.177 4.33382 2.69588 5.00049 3.08078L20.0005 11.741C20.6672 12.1259 20.6672 13.0882 20.0005 13.4731L17.2388 15.1412L17.0055 15.2759M3.50049 8L3.50049 21.2673C3.50049 22.0371 4.33382 22.5182 5.00049 22.1333L14.1192 16.9423L14.4074 16.7759" stroke="currentColor" stroke-width="2" stroke-miterlimit="16" data-play-pause="path" stroke-linecap="round"></path>
</svg>
</button>
<div class="osmo-credits">
<p class="osmo-credits__p">Resource by <a target="_blank" href="https://www.osmo.supply?utm_source=codepen&utm_medium=pen&utm_campaign=gsap-free" class="osmo-credits__p-a">Osmo</a>
</p>
</div>
// ------- Osmo [https://osmo.supply/] ------- //
gsap.registerPlugin(MorphSVGPlugin, Physics2DPlugin);
function initMorphingPlayPauseToggle() {
const playPath =
"M3.5 5L3.50049 3.9468C3.50049 3.177 4.33382 2.69588 5.00049 3.08078L20.0005 11.741C20.6672 12.1259 20.6672 13.0882 20.0005 13.4731L17.2388 15.1412L17.0055 15.2759M3.50049 8L3.50049 21.2673C3.50049 22.0371 4.33382 22.5182 5.00049 22.1333L14.1192 16.9423L14.4074 16.7759";
const pausePath =
"M15.5004 4.05859V5.0638V5.58691V8.58691V15.5869V19.5869V21.2549M8.5 3.96094V10.3721V17V19L8.5 21";
const buttonToggle = document.querySelector('[data-play-pause="toggle"]');
const iconPath = buttonToggle.querySelector('[data-play-pause="path"]');
let isPlaying = false; // keep track of state to control the morph
buttonToggle.addEventListener("click", () => {
gsap.to(iconPath, {
duration: 0.5,
morphSVG: {
type: "rotational",
map: "complexity", // morphs the shape based on the amount of anchor points, it's fastest!
shape: isPlaying ? playPath : pausePath // if button is 'playing', morph to pause and vice versa
},
ease: "power4.inOut"
});
isPlaying = !isPlaying; // control play/pause state again
});
}
function initConfettiClick() {
document.addEventListener("click", (event) => {
// Generate a random number of dots
const dotCount = gsap.utils.random(15, 30, 1);
const colors = ["#0ae448", "#abff84", "#fffce1"]; // Define colors once
for (let i = 0; i < dotCount; i++) {
// Create a dot element
const dot = document.createElement("div");
dot.classList.add("dot");
// Append the dot to the body
document.body.appendChild(dot);
// Set initial position and styles of the dot
gsap.set(dot, {
backgroundColor: gsap.utils.random(colors), // Pick a random color
top: event.clientY, // position dot at coordinates of the click
left: event.clientX,
scale: 0 // Start at scale 0
});
// Animate the dot with physics2D
gsap
.timeline({
onComplete: () => dot.remove() // Remove the dot after animation
})
.to(dot, {
scale: gsap.utils.random(0.3, 1), // Scale between 0.5 and 1
duration: 0.3, // Quick pop-in effect
ease: "power3.out"
})
.to(
dot,
{
duration: 2,
physics2D: {
velocity: gsap.utils.random(500, 1000), // Random velocity
angle: gsap.utils.random(0, 360), // Random direction
gravity: 1500 // Gravity effect
},
autoAlpha: 0, // Fade out towards the end
ease: "none"
},
"<"
); // Start together with the previous tween
}
});
}
// Initialize Functions
document.addEventListener("DOMContentLoaded", () => {
initMorphingPlayPauseToggle();
initConfettiClick();
});
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script src="https://assets.codepen.io/16327/CustomBounce3.min.js"></script>
<script src="https://assets.codepen.io/16327/CustomEase3.min.js"></script>
<script src="https://assets.codepen.io/16327/CustomWiggle3.min.js"></script>
<script src="https://assets.codepen.io/16327/DrawSVGPlugin3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Draggable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/EaselPlugin.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/EasePack.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Flip.min.js"></script>
<script src="https://assets.codepen.io/16327/GSDevTools3.min.js"></script>
<script src="https://assets.codepen.io/16327/InertiaPlugin.min.js"></script>
<script src="https://assets.codepen.io/16327/MorphSVGPlugin3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/MotionPathPlugin.min.js"></script>
<script src="https://assets.codepen.io/16327/MotionPathHelper.min.js"></script>
<script src="https://assets.codepen.io/16327/PhysicsPropsPlugin3.min.js"></script>
<script src="https://assets.codepen.io/16327/Physics2DPlugin3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/PixiPlugin.min.js"></script>
<script src="https://assets.codepen.io/16327/ScrambleTextPlugin3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollToPlugin.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollTrigger.min.js"></script>
<script src="https://assets.codepen.io/16327/SplitText3.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/TextPlugin.min.js"></script>
<script src="https://assets.codepen.io/16327/ScrollSmoother.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/Observer.min.js"></script>
/* ------- Osmo [https://osmo.supply/] ------- */
body {
background-color: #25122d;
color: #efeeec;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
flex-direction: column;
overflow: clip;
font-size: 1vw;
cursor: url("https://cdn.prod.website-files.com/6708f85ff3d3cba6aff436fb/671251b239d7aeb290a31ac5_cursor-default%402x.svg")
2 0,
auto;
}
a,
button {
cursor: url("https://cdn.prod.website-files.com/6708f85ff3d3cba6aff436fb/671251b212e6b71494aa67ff_cursor-pointer%402x.svg")
12 0,
pointer;
}
.play-pause-button {
background-color: transparent;
border: none;
justify-content: center;
align-items: center;
width: max(7.5rem, 15vw);
height: max(7.5rem, 15vw);
padding: 0;
display: flex;
color: currentColor;
}
.play-pause-icon {
width: 100%;
height: 100%;
}
.dot {
position: absolute;
background-color: #0ae448;
width: 2vw;
height: 2vw;
border-radius: 50%;
will-change: transform, opacity;
pointer-events: none;
}
.osmo-credits {
z-index: 999;
pointer-events: none;
flex-flow: column;
justify-content: center;
align-items: center;
width: 100%;
height: 4em;
padding: 1em;
display: flex;
position: fixed;
bottom: 0;
left: 0;
}
.osmo-credits__p {
pointer-events: auto;
color: #efeeec80;
text-align: center;
margin: 0;
font-family: PP Neue Montreal, Arial, sans-serif;
font-size: 1.125em;
font-weight: 500;
line-height: 1.3;
}
.osmo-credits__p-a {
color: #efeeec;
}
@font-face {
font-family: "PP Neue Montreal";
src: url("https://cdn.prod.website-files.com/6819ed8312518f61b84824df/6819ed8312518f61b84825ba_PPNeueMontreal-Medium.woff2")
format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
<link href="https://slater.app/10324/23333.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment