HTML5 Canvas Shape Tango with Konva
HTML5 Canvas Shape Tango with Konva
This demo shows how to create animated shapes that dance around the canvas when triggered. It demonstrates:
- Creating random shapes with different properties
- Using Konva's tweening system for smooth animations
- Handling user interactions (drag and drop, button clicks)
- Managing multiple animations simultaneously
Instructions: Drag and drop the shapes to position them, then click the "Tango!" button to make them dance around the canvas. Each shape will move to a random position, rotate, change size, and color. Refresh the page to generate new random shapes.
- Vanilla
- React
- Vue
import Konva from 'konva'; // Create button const button = document.createElement('button'); button.textContent = 'Tango!'; button.style.position = 'absolute'; button.style.top = '10px'; button.style.left = '10px'; button.style.padding = '10px'; document.body.appendChild(button); const stage = new Konva.Stage({ container: 'container', width: window.innerWidth, height: window.innerHeight, }); const layer = new Konva.Layer(); stage.add(layer); const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']; function getRandomColor() { return colors[Math.floor(Math.random() * colors.length)]; } function tango(layer) { layer.getChildren().forEach((shape) => { const radius = Math.random() * 100 + 20; new Konva.Tween({ node: shape, duration: 1, x: Math.random() * stage.width(), y: Math.random() * stage.height(), rotation: Math.random() * 360, radius: radius, opacity: (radius - 20) / 100, easing: Konva.Easings.EaseInOut, fill: getRandomColor(), }).play(); }); } // Create initial shapes for (let n = 0; n < 10; n++) { const radius = Math.random() * 100 + 20; const shape = new Konva.RegularPolygon({ x: Math.random() * stage.width(), y: Math.random() * stage.height(), sides: Math.ceil(Math.random() * 5 + 3), radius: radius, fill: getRandomColor(), opacity: (radius - 20) / 100, draggable: true, }); layer.add(shape); } button.addEventListener('click', () => tango(layer));
import React from 'react'; import { Stage, Layer, RegularPolygon } from 'react-konva'; const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']; const NUM_SHAPES = 10; const getRandomColor = () => COLORS[Math.floor(Math.random() * COLORS.length)]; const getRandomShapeProps = (width, height) => { const radius = Math.random() * 100 + 20; return { x: Math.random() * width, y: Math.random() * height, sides: Math.ceil(Math.random() * 5 + 3), radius, fill: getRandomColor(), opacity: (radius - 20) / 100, }; }; const App = () => { const [shapes, setShapes] = React.useState([]); const stageRef = React.useRef(); React.useEffect(() => { const initialShapes = Array.from({ length: NUM_SHAPES }, () => getRandomShapeProps(window.innerWidth, window.innerHeight) ); setShapes(initialShapes); }, []); const handleDragEnd = (e, index) => { const newShapes = [...shapes]; newShapes[index] = { ...newShapes[index], x: e.target.x(), y: e.target.y(), }; setShapes(newShapes); }; const handleTango = () => { const layer = stageRef.current.findOne('Layer'); const shapeNodes = layer.find('RegularPolygon'); shapeNodes.forEach((node, i) => { const radius = Math.random() * 100 + 20; const newProps = { duration: 1, x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, rotation: Math.random() * 360, radius: radius, opacity: (radius - 20) / 100, easing: Konva.Easings.EaseInOut, fill: getRandomColor(), }; node.to(newProps); // Update state after animation setTimeout(() => { setShapes(prev => { const newShapes = [...prev]; newShapes[i] = { ...newShapes[i], ...newProps }; return newShapes; }); }, 1000); }); }; return ( <> <Stage width={window.innerWidth} height={window.innerHeight} ref={stageRef} > <Layer> {shapes.map((shape, i) => ( <RegularPolygon key={i} {...shape} draggable onDragEnd={(e) => handleDragEnd(e, i)} /> ))} </Layer> </Stage> <button onClick={handleTango} style={{ position: 'absolute', top: '10px', left: '10px', padding: '10px', }} > Tango! </button> </> ); }; export default App;
<template> <div> <v-stage :config="stageConfig" ref="stageRef" > <v-layer> <v-regular-polygon v-for="(shape, i) in shapes" :key="i" :config="{ ...shape, draggable: true }" @dragend="(e) => handleDragEnd(e, i)" /> </v-layer> </v-stage> <button @click="handleTango" style="position: absolute; top: 10px; left: 10px; padding: 10px" > Tango! </button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'purple']; const NUM_SHAPES = 10; const shapes = ref([]); const stageRef = ref(null); const stageConfig = { width: window.innerWidth, height: window.innerHeight, }; const getRandomColor = () => COLORS[Math.floor(Math.random() * COLORS.length)]; const getRandomShapeProps = () => { const radius = Math.random() * 100 + 20; return { x: Math.random() * stageConfig.width, y: Math.random() * stageConfig.height, sides: Math.ceil(Math.random() * 5 + 3), radius, fill: getRandomColor(), opacity: (radius - 20) / 100, }; }; const handleDragEnd = (e, index) => { const newShapes = [...shapes.value]; newShapes[index] = { ...newShapes[index], x: e.target.x(), y: e.target.y(), }; shapes.value = newShapes; }; const handleTango = () => { const stage = stageRef.value.getStage(); const layer = stage.findOne('Layer'); const shapeNodes = layer.find('RegularPolygon'); shapeNodes.forEach((node, i) => { const radius = Math.random() * 100 + 20; const newProps = { duration: 1, x: Math.random() * stageConfig.width, y: Math.random() * stageConfig.height, rotation: Math.random() * 360, radius: radius, opacity: (radius - 20) / 100, easing: Konva.Easings.EaseInOut, fill: getRandomColor(), }; node.to(newProps); // Update state after animation setTimeout(() => { const newShapes = [...shapes.value]; newShapes[i] = { ...newShapes[i], ...newProps }; shapes.value = newShapes; }, 1000); }); }; onMounted(() => { shapes.value = Array.from({ length: NUM_SHAPES }, getRandomShapeProps); }); </script>