<button class="button-star">
<canvas></canvas>
<div class="label">
<span class="default">Star<span class="success">red</span></span>
</div>
<div class="number">
<span class="current">31</span>
<span class="new">32</span>
<div class="add">+1</div>
</div>
</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/js/loaders/DRACOLoader.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r110/examples/js/loaders/GLTFLoader.js"></script>
<!-- twitter -->
<a class="twitter" target="_top" href="https://twitter.com/aaroniker_me"><svg xmlns="http://www.w3.org/2000/svg" width="72" height="72" viewBox="0 0 72 72"><path d="M67.812 16.141a26.246 26.246 0 0 1-7.519 2.06 13.134 13.134 0 0 0 5.756-7.244 26.127 26.127 0 0 1-8.313 3.176A13.075 13.075 0 0 0 48.182 10c-7.229 0-13.092 5.861-13.092 13.093 0 1.026.118 2.021.338 2.981-10.885-.548-20.528-5.757-26.987-13.679a13.048 13.048 0 0 0-1.771 6.581c0 4.542 2.312 8.551 5.824 10.898a13.048 13.048 0 0 1-5.93-1.638c-.002.055-.002.11-.002.162 0 6.345 4.513 11.638 10.504 12.84a13.177 13.177 0 0 1-3.449.457c-.846 0-1.667-.078-2.465-.231 1.667 5.2 6.499 8.986 12.23 9.09a26.276 26.276 0 0 1-16.26 5.606A26.21 26.21 0 0 1 4 55.976a37.036 37.036 0 0 0 20.067 5.882c24.083 0 37.251-19.949 37.251-37.249 0-.566-.014-1.134-.039-1.694a26.597 26.597 0 0 0 6.533-6.774z"></path></svg></a>
.button-star {
--button-label-x: 24px;
--button-label-success-opacity: 0;
--button-star-scale: 0.75;
--button-star-greyscale: 85%;
--button-star-hue: 170deg;
--button-star-opacity: 0.45;
--button-star-add-opacity: 0;
--button-star-add-y: 16px;
--button-star-current-opacity: 1;
--button-star-current-y: 0px;
--button-star-new-opacity: 0;
--button-star-new-y: 16px;
appearance: none;
border: none;
cursor: pointer;
background-color: #1f2024;
color: #fff;
border-radius: 13px;
outline: none;
margin: 0;
padding: 0;
font-family: "Poppins";
font-size: 14px;
font-weight: 600;
line-height: 20px;
position: relative;
text-align: center;
display: flex;
box-shadow: inset 0 0 0 1px #35373f, 0px 1px 3px rgba(52, 54, 63, 0.1),
0px 4px 10px rgba(52, 54, 63, 0.15);
transition: transform 0.15s;
transform: translateZ(0);
&:active {
transform: scale(0.985, 0.98) translateZ(0);
}
canvas {
display: block;
width: 400px;
height: 200px;
position: absolute;
z-index: 1;
left: -176px;
top: -84px;
pointer-events: none;
filter: Grayscale(var(--button-star-greyscale))
hue-rotate(var(--button-star-hue));
transform: scale(var(--button-star-scale));
transform-origin: 50% 52%;
opacity: var(--button-star-opacity);
}
.label {
width: 90px;
padding: 10px 0;
transform: translateZ(0);
.default {
display: block;
transform: translateX(var(--button-label-x));
.success {
opacity: var(--button-label-success-opacity);
}
}
}
.number {
padding: 10px 0;
width: 44px;
position: relative;
transform: translateZ(0);
&:before {
content: "";
position: absolute;
left: 0;
top: 1px;
bottom: 1px;
width: 1px;
background-color: #35373f;
}
.current {
color: #8a8d9b;
opacity: var(--button-star-current-opacity);
transform: translateY(var(--button-star-current-y));
transition: color 0.2s;
display: block;
}
.new {
opacity: var(--button-star-new-opacity);
transform: translateY(var(--button-star-new-y));
color: #fbfaaa;
position: absolute;
top: 10px;
left: 0;
right: 0;
display: block;
}
.add {
position: absolute;
bottom: 100%;
left: 0;
right: 0;
opacity: var(--button-star-add-opacity);
transform: translateY(var(--button-star-add-y));
pointer-events: none;
color: #d0d0db;
display: block;
}
}
}
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: inherit;
&:before,
&:after {
box-sizing: inherit;
}
}
// Center & dribbble
body {
min-height: 100vh;
display: flex;
font-family: 'Inter', Arial;
justify-content: center;
align-items: center;
background: #f4f5fa;
.twitter {
position: fixed;
display: block;
right: 12px;
bottom: 12px;
svg {
width: 32px;
height: 32px;
fill: #1da1f2;
}
}
}
View Compiled
const addLight = (x, y, z, i, s) => {
const light = new THREE.PointLight(0xffffff, i);
light.position.set(x, y, z);
s.add(light);
};
document.querySelectorAll(".button-star").forEach((button) => {
const width = 400;
const height = 200;
const canvas = button.querySelector("canvas");
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
context: canvas.getContext("webgl2"),
antialias: true,
alpha: true,
});
renderer.setSize(width, height);
renderer.setPixelRatio(4);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, width / height, 4, 100);
camera.position.set(0, 0, 30);
addLight(0, 1, 5, 0.2, scene);
addLight(0, 2, 0, 0.3, scene);
addLight(5, 0, 1, 0.2, scene);
addLight(-5, 0, 1, 0.2, scene);
addLight(3, 0, 5, 0.6, scene);
scene.add(new THREE.AmbientLight(0xffffff));
const loader = new THREE.GLTFLoader();
loader.setCrossOrigin("anonymous");
loader.setDRACOLoader(new THREE.DRACOLoader());
loader.load(
"https://assets.codepen.io/165585/star-default.glb",
function (data) {
const object = data.scene;
object.position.set(0, -0.5, 0);
scene.add(object);
let scaleTween, rotateTweenXZ, rotateTweenY, rotateTweenZ;
button.addEventListener("pointerenter", () => {
if (button.classList.contains("active")) {
return;
}
scaleTween = gsap.to(button, {
"--button-star-scale": 1,
ease: "elastic.out(1, .75)",
duration: 0.5,
});
gsap.to(button, {
"--button-star-greyscale": "0%",
"--button-star-hue": "0deg",
"--button-star-opacity": 1,
duration: 0.15,
});
rotateTweenXZ = gsap.to(object.rotation, {
duration: 0.25,
x: THREE.Math.degToRad(4),
z: THREE.Math.degToRad(12),
ease: "none",
});
rotateTweenY = gsap.to(object.rotation, {
duration: 1.5,
y: THREE.Math.degToRad(360),
ease: "none",
repeat: -1,
});
rotateTweenZ = gsap.to(object.rotation, {
duration: 4.5,
keyframes: [
{
ease: "none",
z: THREE.Math.degToRad(5),
},
{
ease: "none",
z: THREE.Math.degToRad(12),
},
],
repeat: -1,
});
});
button.addEventListener("pointerleave", () => {
if (button.classList.contains("active")) {
return;
}
scaleTween.kill();
rotateTweenXZ.kill();
rotateTweenY.kill();
rotateTweenZ.kill();
gsap.to(button, {
"--button-star-scale": 0.75,
"--button-star-greyscale": "85%",
"--button-star-hue": "170deg",
"--button-star-opacity": 0.45,
duration: 0.2,
});
gsap.to(object.rotation, {
duration: 0.3,
x: THREE.Math.degToRad(0),
y: THREE.Math.degToRad(0),
z: THREE.Math.degToRad(0),
});
});
button.addEventListener("click", () => {
if (button.classList.contains("active")) {
gsap.to(button, {
"--button-label-x": "24px",
"--button-label-success-opacity": 0,
"--button-star-opacity": 0.45,
"--button-star-current-opacity": 1,
"--button-star-current-y": "0px",
"--button-star-new-opacity": 0,
"--button-star-new-y": "16px",
duration: 0.25,
clearProps: true,
onComplete() {
button.classList.remove("active");
},
});
return;
}
scaleTween.kill();
rotateTweenY.kill();
button.classList.add("active");
gsap.to(button, {
"--button-star-current-opacity": 0,
"--button-star-current-y": "-16px",
"--button-star-new-opacity": 1,
"--button-star-new-y": "0px",
"--button-star-add-opacity": 1,
duration: 0.25,
delay: 0.5,
});
gsap.to(button, {
"--button-label-x": "0px",
"--button-label-success-opacity": 1,
duration: 0.25,
delay: 0.2,
});
gsap.to(button, {
"--button-star-add-opacity": 0,
duration: 0.2,
delay: 0.75,
});
gsap.to(button, {
"--button-star-add-y": "-8px",
duration: 0.5,
delay: 0.5,
});
gsap.to(button, {
"--button-star-scale": 1,
"--button-star-greyscale": "0%",
"--button-star-hue": "0deg",
"--button-star-opacity": 1,
duration: 0.15,
});
gsap.to(object.rotation, {
duration: 0.4,
y: THREE.Math.degToRad(0),
});
gsap.to(object.position, {
duration: 0.7,
motionPath: {
path: [
{
x: 0,
y: -0.5,
},
{
x: 5.45,
y: -5,
},
{
x: 10.9,
y: -0.5,
},
{
x: 7,
y: 7,
},
],
curviness: 1.2,
},
ease: "sine.in",
});
gsap.to(button, {
"--button-star-opacity": 0,
duration: 0.1,
delay: 0.6,
onComplete() {
object.rotation.x = THREE.Math.degToRad(0);
object.rotation.y = THREE.Math.degToRad(0);
object.rotation.z = THREE.Math.degToRad(0);
object.position.set(0, -0.5, 0);
gsap.set(button, {
"--button-star-scale": 0.75,
"--button-star-greyscale": "85%",
"--button-star-hue": "170deg",
"--button-star-opacity": 0,
});
},
});
});
}
);
const render = () => {
requestAnimationFrame(render);
renderer.render(scene, camera);
};
render();
});