拖放压力测试与 10,000 个形状
本示例展示了一个包含 10,000 个形状的压力测试。为简化操作,我们只使用了两个图层 - 一个主要图层用于所有形状,一个专用的拖动图层。当我们拖动一个形状时,它会移动到单独的拖动图层上,以确保移动的流畅性。
- 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 mainLayer = new Konva.Layer();
// 创建一个专用图层用于拖动
const dragLayer = new Konva.Layer();
// 定义随机形状的颜色
const colors = [
'red',
'orange',
'yellow',
'green',
'blue',
'cyan',
'purple',
];
let colorIndex = 0;
// 辅助函数,将圆添加到图层
function addCircle(layer) {
const color = colors[colorIndex++];
if (colorIndex >= colors.length) {
colorIndex = 0;
}
const randX = Math.random() * stage.width();
const randY = Math.random() * stage.height();
const circle = new Konva.Circle({
x: randX,
y: randY,
radius: 6,
fill: color,
});
layer.add(circle);
}
// 在主要图层上创建 10,000 个圆
for (let n = 0; n < 10000; n++) {
addCircle(mainLayer);
}
// 将主要图层和拖动图层添加到舞台
stage.add(mainLayer);
stage.add(dragLayer);
// 设置拖放行为
stage.on('mousedown', function (evt) {
const circle = evt.target;
// 仅处理圆形(忽略在空白区域的点击)
if (!circle || circle.getClassName() !== 'Circle') {
return;
}
// 将圆形移动到拖动图层
circle.moveTo(dragLayer);
circle.startDrag();
});
// 当拖动停止时,将圆形移动回主要图层
stage.on('mouseup', function (evt) {
const circle = evt.target;
// 仅处理圆形
if (!circle || circle.getClassName() !== 'Circle') {
return;
}
// 将圆形移回主要图层
circle.moveTo(mainLayer);
});
import { useState, useEffect, useRef } from 'react';
import { Stage, Layer, Circle } from 'react-konva';
const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'cyan', 'purple'];
const SHAPE_COUNT = 10000;
const App = () => {
// 用于存储所有圆形数据的状态
const [circles, setCircles] = useState([]);
// 图层的 refs
const mainLayerRef = useRef(null);
const dragLayerRef = useRef(null);
// 初始化圆形数据
useEffect(() => {
const circlesData = [];
// 创建 10,000 个圆形
for (let i = 0; i < SHAPE_COUNT; i++) {
circlesData.push({
id: i,
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: 6,
fill: COLORS[i % COLORS.length]
});
}
setCircles(circlesData);
}, []);
// 这不是典型的"React 方式”管理组件。
// 在更符合 React 的方式中,我们会更新状态,并让 React 处理 DOM。
// 然而为了本性能演示,我们直接操作节点
// 以匹配原生 JS 实现。
const handleDragStart = (e) => {
const target = e.target;
// 将圆形移动到拖动图层
target.moveTo(dragLayerRef.current);
};
const handleDragEnd = (e) => {
const target = e.target;
const id = target.id();
const { x, y } = target.position();
// 将圆形移回主要图层
target.moveTo(mainLayerRef.current);
// 存储最终位置,而无需在每次移动时重新渲染 10,000 个节点。
setCircles(currentCircles => currentCircles.map(circle =>
String(circle.id) === String(id) ? { ...circle, x, y } : circle
));
};
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
{/* 用于所有圆形的主要图层 */}
<Layer ref={mainLayerRef}>
{circles.map(circle => (
<Circle
key={circle.id}
id={circle.id}
x={circle.x}
y={circle.y}
radius={circle.radius}
fill={circle.fill}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
/>
))}
</Layer>
{/* 空拖动图层,在拖动期间接收圆形 */}
<Layer ref={dragLayerRef} />
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageConfig">
<!-- 用于所有圆形的主要图层 -->
<v-layer ref="mainLayer">
<v-circle
v-for="circle in circles"
:key="circle.id"
:config="{
id: circle.id,
x: circle.x,
y: circle.y,
radius: 6,
fill: circle.fill,
draggable: true
}"
@dragstart="handleDragStart"
@dragend="handleDragEnd"
/>
</v-layer>
<!-- 空拖动图层,在拖动期间接收圆形 -->
<v-layer ref="dragLayer" />
</v-stage>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const COLORS = ['red', 'orange', 'yellow', 'green', 'blue', 'cyan', 'purple'];
const SHAPE_COUNT = 10000;
const stageConfig = {
width: window.innerWidth,
height: window.innerHeight
};
// 图层的 refs
const mainLayer = ref(null);
const dragLayer = ref(null);
// 圆形的状态
const circles = ref([]);
// 初始化圆形数据
onMounted(() => {
const circlesData = [];
// 创建 10,000 个圆形
for (let i = 0; i < SHAPE_COUNT; i++) {
circlesData.push({
id: i,
x: Math.random() * stageConfig.width,
y: Math.random() * stageConfig.height,
radius: 6,
fill: COLORS[i % COLORS.length]
});
}
circles.value = circlesData;
});
// 这不是典型的"Vue 方式”管理组件。
// 在更符合 Vue 的方式中,我们会更新状态,并让 Vue 处理 DOM。
// 然而为了本性能演示,我们直接操作节点
// 以匹配原生 JS 实现。
const handleDragStart = (e) => {
const target = e.target;
// 将圆形移动到拖动图层
target.moveTo(dragLayer.value.getNode());
};
const handleDragEnd = (e) => {
const target = e.target;
// 将圆形移回主要图层
target.moveTo(mainLayer.value.getNode());
};
</script>