跳跃兔子性能压力测试
跳跃兔子性能压力测试
本演示展示了同时移动许多 Konva.Image
对象的性能。这是基于 PixiJS 框架的 Bunnymark 演示进行的改编。
注意:您可能会注意到,Konva
版本的性能比原始的 PixiJS
版本慢。这是因为 PixiJS 对WebGL渲染和此类动画进行了高度优化。而 Konva 仍在不断优化其内部实现,请记住此演示并不代表使用 Konva 构建的典型应用的性能。
对于具有大量动画对象的应用,您可以考虑使用 Native Canvas 访问 或甚至其他框架。根据您的具体应用需求选择合适的工具。
操作指南: 点击或触摸画布以添加更多兔子。计数器将显示当前正在动画的兔子数量。
- Vanilla
- React
- Vue
import Konva from 'konva'; // 设置舞台和图层 const width = window.innerWidth; const height = window.innerHeight; const stage = new Konva.Stage({ container: 'container', width: width, height: height, }); const layer = new Konva.FastLayer(); stage.add(layer); // 创建状态显示和计数器 const counterDiv = document.createElement('div'); counterDiv.style.position = 'absolute'; counterDiv.style.top = '50px'; counterDiv.style.backgroundColor = 'white'; counterDiv.style.fontSize = '12px'; counterDiv.style.padding = '5px'; counterDiv.innerHTML = '0 BUNNIES'; document.getElementById('container').appendChild(counterDiv); // 定义变量 const bunnys = []; const GRAVITY = 0.75; const maxX = width; const minX = 0; const maxY = height; const minY = 0; const startBunnyCount = 100; // 初始较少兔子以提升初始性能 const amount = 10; // 每次添加的兔子数量 let isAdding = false; let count = 0; let wabbitTexture; // 载入兔子图片 wabbitTexture = new Image(); wabbitTexture.onload = function() { addBunnies(startBunnyCount); counterDiv.innerHTML = startBunnyCount + ' BUNNIES'; count = startBunnyCount; // 开始动画循环 requestAnimationFrame(update); }; wabbitTexture.src = 'https://konvajs.org/assets/bunny.png'; // 添加事件监听 stage.on('mousedown touchstart', function() { isAdding = true; }); stage.on('mouseup touchend', function() { isAdding = false; }); // 添加兔子函数 function addBunnies(num) { for (let i = 0; i < num; i++) { const bunny = new Konva.Image({ image: wabbitTexture, transformsEnabled: 'position', perfectDrawEnabled: false, x: Math.random() * width, y: Math.random() * height, }); bunny.speedX = Math.random() * 10; bunny.speedY = Math.random() * 10 - 5; bunnys.push(bunny); layer.add(bunny); } } // 动画更新函数 function update() { // 如果鼠标按下,添加更多兔子 if (isAdding) { addBunnies(amount); count += amount; counterDiv.innerHTML = count + ' BUNNIES'; } // 更新所有兔子位置 for (let i = 0; i < bunnys.length; i++) { const bunny = bunnys[i]; let x = bunny.x(); let y = bunny.y(); x += bunny.speedX; y += bunny.speedY; bunny.speedY += GRAVITY; // 边界反弹 if (x > maxX - wabbitTexture.width) { bunny.speedX *= -1; x = maxX - wabbitTexture.width; } else if (x < minX) { bunny.speedX *= -1; x = minX; } if (y > maxY - wabbitTexture.height) { bunny.speedY *= -0.85; y = maxY - wabbitTexture.height; if (Math.random() > 0.5) { bunny.speedY -= Math.random() * 6; } } else if (y < minY) { bunny.speedY = 0; y = minY; } bunny.position({ x, y }); } layer.batchDraw(); requestAnimationFrame(update); }
import { useState, useEffect, useRef } from 'react'; import { Stage, FastLayer, Image } from 'react-konva'; import { useImage } from 'react-konva-utils'; const BunnyMark = () => { // 常量定义 const width = window.innerWidth; const height = window.innerHeight; const GRAVITY = 0.75; const START_COUNT = 100; const ADD_AMOUNT = 10; // 状态和引用 const [count, setCount] = useState(0); const [isAdding, setIsAdding] = useState(false); const layerRef = useRef(null); const bunniesRef = useRef([]); const bunnyNodesRef = useRef([]); // 存储实际 Konva 节点的引用 const [bunnyImage] = useImage('https://konvajs.org/assets/bunny.png'); // 创建一个兔子对象(位置和速度) const createBunny = (x, y) => ({ x, y, speedX: Math.random() * 10, speedY: Math.random() * 10 - 5 }); // 存储节点引用 const storeNodeRef = (index, node) => { if (node) { bunnyNodesRef.current[index] = node; } }; // 图片加载完成后初始化兔子 useEffect(() => { if (!bunnyImage) return; const initialBunnies = Array(START_COUNT).fill(0).map(() => createBunny( Math.random() * width, Math.random() * height )); bunniesRef.current = initialBunnies; bunnyNodesRef.current = new Array(START_COUNT); setCount(START_COUNT); }, [bunnyImage]); // 动画更新循环 useEffect(() => { if (!bunnyImage) return; let animationFrameId; const update = () => { // 如果需要,添加更多兔子 if (isAdding) { const currentLength = bunniesRef.current.length; const newBunnies = Array(ADD_AMOUNT).fill(0).map(() => createBunny( Math.random() * width, Math.random() * height ) ); bunniesRef.current = [...bunniesRef.current, ...newBunnies]; // 扩展节点数组以容纳新兔子 bunnyNodesRef.current = [...bunnyNodesRef.current, ...new Array(ADD_AMOUNT)]; setCount(prevCount => prevCount + ADD_AMOUNT); } // 更新所有兔子位置 - 使用直接节点操作以提升性能 // 避免频繁React重渲染 bunniesRef.current.forEach((bunny, i) => { // 更新模型数据 bunny.x += bunny.speedX; bunny.y += bunny.speedY; bunny.speedY += GRAVITY; // 边界反弹 if (bunny.x > width - bunnyImage.width) { bunny.speedX *= -1; bunny.x = width - bunnyImage.width; } else if (bunny.x < 0) { bunny.speedX *= -1; bunny.x = 0; } if (bunny.y > height - bunnyImage.height) { bunny.speedY *= -0.85; bunny.y = height - bunnyImage.height; if (Math.random() > 0.5) { bunny.speedY -= Math.random() * 6; } } else if (bunny.y < 0) { bunny.speedY = 0; bunny.y = 0; } // 直接更新节点(替代 react 的频繁重绘) const node = bunnyNodesRef.current[i]; if (node) { node.x(bunny.x); node.y(bunny.y); } }); // 一次批量绘制图层,避免逐个节点调用 if (layerRef.current) { layerRef.current.getLayer().batchDraw(); } animationFrameId = requestAnimationFrame(update); }; update(); return () => { cancelAnimationFrame(animationFrameId); }; }, [isAdding, bunnyImage]); // 触摸/鼠标事件处理 const handleDown = () => setIsAdding(true); const handleUp = () => setIsAdding(false); if (!bunnyImage) return <div>加载兔子图片中...</div>; return ( <> <Stage width={width} height={height} onMouseDown={handleDown} onMouseUp={handleUp} onTouchStart={handleDown} onTouchEnd={handleUp} > <FastLayer ref={layerRef}> {bunniesRef.current.map((bunny, i) => ( <Image key={i} ref={(node) => storeNodeRef(i, node)} image={bunnyImage} x={bunny.x} y={bunny.y} transformsEnabled="position" perfectDrawEnabled={false} /> ))} </FastLayer> </Stage> <div style={{ position: 'absolute', top: '50px', backgroundColor: 'white', fontSize: '12px', padding: '5px' }} > {count} BUNNIES </div> </> ); }; export default BunnyMark;
<template> <div> <v-stage :config="stageConfig" @mousedown="isAdding = true" @mouseup="isAdding = false" @touchstart="isAdding = true" @touchend="isAdding = false" > <v-fast-layer ref="layerRef"> <v-image v-for="(bunny, index) in bunnies" :key="index" :config="{ image: bunnyImage, x: bunny.x, y: bunny.y, transformsEnabled: 'position', perfectDrawEnabled: false }" :ref="el => storeNodeRef(index, el)" /> </v-fast-layer> </v-stage> <div style="position: absolute; top: 50px; background-color: white; font-size: 12px; padding: 5px;" > {{ count }} BUNNIES </div> </div> </template> <script setup> import { ref, reactive, onMounted, onUnmounted } from 'vue'; // 舞台配置 const stageConfig = { width: window.innerWidth, height: window.innerHeight }; // 引用 const layerRef = ref(null); const bunnyImage = ref(null); const bunnies = ref([]); const count = ref(0); const isAdding = ref(false); const animationFrameId = ref(null); const bunnyNodesMap = ref({}); // 存储Konva节点引用(索引对应) // 常量 const GRAVITY = 0.75; const START_COUNT = 100; const ADD_AMOUNT = 10; // 存储Konva图像节点引用 const storeNodeRef = (index, el) => { if (el) { bunnyNodesMap.value[index] = el; } }; // 创建一个具有位置和速度的兔子 const createBunny = (x, y) => ({ x, y, speedX: Math.random() * 10, speedY: Math.random() * 10 - 5 }); // 动画更新函数 - 使用直接节点操作提升性能 const update = () => { // 如果需要,添加更多兔子 if (isAdding.value) { const newBunnies = Array(ADD_AMOUNT) .fill(0) .map(() => createBunny( Math.random() * stageConfig.width, Math.random() * stageConfig.height ) ); // 通过 push 方式添加新兔子,避免频繁修改数组 bunnies.value.push(...newBunnies); count.value += ADD_AMOUNT; } // 更新所有兔子位置 - 直接操作节点,性能更优 bunnies.value.forEach((bunny, index) => { // 更新模型数据 bunny.x += bunny.speedX; bunny.y += bunny.speedY; bunny.speedY += GRAVITY; // 边界反弹 if (bunny.x > stageConfig.width - bunnyImage.value.width) { bunny.speedX *= -1; bunny.x = stageConfig.width - bunnyImage.value.width; } else if (bunny.x < 0) { bunny.speedX *= -1; bunny.x = 0; } if (bunny.y > stageConfig.height - bunnyImage.value.height) { bunny.speedY *= -0.85; bunny.y = stageConfig.height - bunnyImage.value.height; if (Math.random() > 0.5) { bunny.speedY -= Math.random() * 6; } } else if (bunny.y < 0) { bunny.speedY = 0; bunny.y = 0; } // 如果有引用,直接更新节点(比 Vue 频繁重绘快很多) const node = bunnyNodesMap.value[index]; if (node) { // 获取 Konva 节点并直接设置位置 const konvaNode = node.getNode(); if (konvaNode) { konvaNode.x(bunny.x); konvaNode.y(bunny.y); } } }); // 一次性批量重绘图层,避免逐个调用节点 if (layerRef.value) { layerRef.value.getNode().batchDraw(); } animationFrameId.value = requestAnimationFrame(update); }; // 图片加载 onMounted(() => { const img = new Image(); img.src = 'https://konvajs.org/assets/bunny.png'; img.onload = () => { bunnyImage.value = img; // 添加初始兔子 const initialBunnies = Array(START_COUNT) .fill(0) .map(() => createBunny( Math.random() * stageConfig.width, Math.random() * stageConfig.height ) ); bunnies.value = initialBunnies; count.value = START_COUNT; // 启动动画 animationFrameId.value = requestAnimationFrame(update); }; // 组件卸载时取消动画循环 onUnmounted(() => { if (animationFrameId.value) { cancelAnimationFrame(animationFrameId.value); } }); }); </script>