<div class="stage">
<canvas></canvas>
<button class="toggle">Toggle material</button>
</div>
<script type="module">
import { animate, frame } from "motion"
import { threeEffect } from "motion/three"
import * as THREE from "three"
animate.addEffect(threeEffect)
/**
* Read colours from the page's CSS tokens. animate() accepts any CSS
* colour string for Three.js Color properties.
*/
const token = (name) =>
getComputedStyle(document.body).getPropertyValue(name).trim()
const mint = token("--hue-6")
const pink = token("--hue-1")
const canvas = document.querySelector("canvas")
function runMaterialMotion(mesh, material) {
let active = false
document.querySelector(".toggle").addEventListener("click", () => {
active = !active
/**
* Material properties can be animated through the mesh. Motion looks
* on the object first, then on its material, so transforms and
* material values can share the same animation.
*/
animate(
mesh,
active
? { rotateY: 180, scale: 1.2, color: pink, opacity: 0.6 }
: { rotateY: 0, scale: 1, color: mint, opacity: 1 },
{
type: "spring",
visualDuration: 0.7,
bounce: 0.3,
color: { duration: 0.8, ease: "easeInOut" },
opacity: { duration: 0.8, ease: "easeInOut" },
}
)
/**
* Or animate the material directly
*/
animate(
material,
active
? { roughness: 0.15, metalness: 0.6 }
: { roughness: 0.7, metalness: 0.1 },
{ duration: 0.8, ease: "easeInOut" }
)
})
}
const renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
alpha: true,
})
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
35,
canvas.clientWidth / canvas.clientHeight,
0.1,
100
)
camera.position.z = 5
scene.add(new THREE.HemisphereLight(token("--white"), token("--layer"), 2))
const keyLight = new THREE.DirectionalLight(token("--white"), 3)
keyLight.position.set(3, 4, 5)
scene.add(keyLight)
const material = new THREE.MeshStandardMaterial({
color: mint,
roughness: 0.7,
metalness: 0.1,
transparent: true,
})
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2, 1), material)
scene.add(mesh)
frame.render(() => renderer.render(scene, camera), true)
runMaterialMotion(mesh, material)
</script>
<style>
.stage {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
width: 480px;
max-width: 100vw;
}
.stage canvas {
display: block;
width: 100%;
aspect-ratio: 3 / 2;
}
.stage button {
background-color: var(--hue-1);
color: var(--white);
font-size: 15px;
padding: 10px 20px;
border-radius: 0;
border: none;
cursor: pointer;
}
</style>