跳跃兔子性能压力测试
跳跃兔子性能压力测试
此演示展示了同时移动许多 Konva.Image 对象时的性能。它改编自 PixiJS 框架的 Bunnymark 演示。
注意:您可能会注意到,Konva 版本的性能比原始的 PixiJS 版本慢。这是因为 PixiJS 对 WebGL 渲染和此类动画进行了高度优化。而 Konva 仍在不断优化其内部实现,请记住此演示并不代表使用 Konva 构建的典型应用的性能。
对于具有大量动画对象的应用,您可以考虑使用 Native Canvas 访问 或甚至其他框架。根据您的具体应用需求选择合适的工具。
操作指南: 点击或触摸画布以添加更多兔子。计数器将显示当前正在动画的兔子数量。
- 原生
- 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.Layer({ listening: false });
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, Layer, 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}
>
<Layer ref={layerRef} listening={false}>
{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}
/>
))}
</Layer>
</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-layer ref="layerRef" :config="{ listening: false }">
<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-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>