HTML5 Canvas 优化描边性能技巧
在 Konva 中绘制同时具有描边和阴影的图形时,会执行一个额外的内部绘制步骤。 这是因为 Konva 需要确保正确绘制描边的阴影。 但是,这会影响性能,尤其是在处理大量图形时。
要优化性能,可以设置 shadowForStrokeEnabled(false) 以禁用描边阴影。
当不需要描边投射阴影时,此设置特别有用。
以下示例展示了启用和禁用描边阴影时的性能差异:
- Vanilla
- React
- Vue
import Konva from 'konva'; const stage = new Konva.Stage({ container: 'container', width: window.innerWidth, height: window.innerHeight, }); const layer = new Konva.Layer(); stage.add(layer); // Create shape with shadow for stroke (default) const circleWithShadow = new Konva.Circle({ x: 100, y: 100, radius: 50, fill: 'red', stroke: 'black', strokeWidth: 4, shadowColor: 'black', shadowBlur: 10, shadowOffset: { x: 5, y: 5 }, shadowOpacity: 0.5, }); // Create shape without shadow for stroke (optimized) const circleOptimized = new Konva.Circle({ x: 250, y: 100, radius: 50, fill: 'red', stroke: 'black', strokeWidth: 4, shadowColor: 'black', shadowBlur: 10, shadowOffset: { x: 5, y: 5 }, shadowOpacity: 0.5, shadowForStrokeEnabled: false, }); // Add labels const defaultLabel = new Konva.Text({ x: 50, y: 170, text: 'With Stroke Shadow', fontSize: 16, }); const optimizedLabel = new Konva.Text({ x: 200, y: 170, text: 'Without Stroke Shadow\n(Better Performance)', fontSize: 16, }); // Add FPS counter const fpsText = new Konva.Text({ x: 10, y: 10, text: 'FPS: 0', fontSize: 16, }); layer.add(circleWithShadow); layer.add(circleOptimized); layer.add(defaultLabel); layer.add(optimizedLabel); layer.add(fpsText); // Create animation to demonstrate performance const anim = new Konva.Animation((frame) => { circleWithShadow.rotation(frame.time * 0.1); circleOptimized.rotation(frame.time * 0.1); // Update FPS counter fpsText.text('FPS: ' + frame.frameRate.toFixed(1)); }, layer); anim.start();
import { Stage, Layer, Circle, Text } from 'react-konva'; import { useEffect, useRef } from 'react'; const App = () => { const circleWithShadowRef = useRef(null); const circleOptimizedRef = useRef(null); const fpsTextRef = useRef(null); useEffect(() => { const anim = new Konva.Animation((frame) => { // Rotate circles circleWithShadowRef.current.rotation(frame.time * 0.1); circleOptimizedRef.current.rotation(frame.time * 0.1); // Update FPS counter fpsTextRef.current.text('FPS: ' + frame.frameRate.toFixed(1)); }, circleWithShadowRef.current.getLayer()); anim.start(); return () => anim.stop(); }, []); return ( <Stage width={window.innerWidth} height={window.innerHeight}> <Layer> {/* Circle with shadow for stroke (default) */} <Circle ref={circleWithShadowRef} x={100} y={100} radius={50} fill="red" stroke="black" strokeWidth={4} shadowColor="black" shadowBlur={10} shadowOffset={{ x: 5, y: 5 }} shadowOpacity={0.5} /> {/* Circle without shadow for stroke (optimized) */} <Circle ref={circleOptimizedRef} x={250} y={100} radius={50} fill="red" stroke="black" strokeWidth={4} shadowColor="black" shadowBlur={10} shadowOffset={{ x: 5, y: 5 }} shadowOpacity={0.5} shadowForStrokeEnabled={false} /> {/* Labels */} <Text x={50} y={170} text="With Stroke Shadow" fontSize={16} /> <Text x={200} y={170} text="Without Stroke Shadow\n(Better Performance)" fontSize={16} /> {/* FPS counter */} <Text ref={fpsTextRef} x={10} y={10} text="FPS: 0" fontSize={16} /> </Layer> </Stage> ); }; export default App;
<template> <v-stage :config="stageSize"> <v-layer ref="layerRef"> <!-- Circle with shadow for stroke (default) --> <v-circle ref="circleWithShadowRef" :config="circleWithShadowConfig" /> <!-- Circle without shadow for stroke (optimized) --> <v-circle ref="circleOptimizedRef" :config="circleOptimizedConfig" /> <!-- Labels --> <v-text :config="defaultLabelConfig" /> <v-text :config="optimizedLabelConfig" /> <!-- FPS counter --> <v-text ref="fpsTextRef" :config="fpsConfig" /> </v-layer> </v-stage> </template> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; import Konva from 'konva'; const stageSize = { width: window.innerWidth, height: window.innerHeight }; const circleWithShadowConfig = { x: 100, y: 100, radius: 50, fill: 'red', stroke: 'black', strokeWidth: 4, shadowColor: 'black', shadowBlur: 10, shadowOffset: { x: 5, y: 5 }, shadowOpacity: 0.5 }; const circleOptimizedConfig = { x: 250, y: 100, radius: 50, fill: 'red', stroke: 'black', strokeWidth: 4, shadowColor: 'black', shadowBlur: 10, shadowOffset: { x: 5, y: 5 }, shadowOpacity: 0.5, shadowForStrokeEnabled: false }; const defaultLabelConfig = { x: 50, y: 170, text: 'With Stroke Shadow', fontSize: 16 }; const optimizedLabelConfig = { x: 200, y: 170, text: 'Without Stroke Shadow\n(Better Performance)', fontSize: 16 }; const fpsConfig = ref({ x: 10, y: 10, text: 'FPS: 0', fontSize: 16 }); const layerRef = ref(null); const circleWithShadowRef = ref(null); const circleOptimizedRef = ref(null); const fpsTextRef = ref(null); let anim = null; onMounted(() => { anim = new Konva.Animation((frame) => { // Rotate circles circleWithShadowRef.value.getNode().rotation(frame.time * 0.1); circleOptimizedRef.value.getNode().rotation(frame.time * 0.1); // Update FPS counter fpsTextRef.value.getNode().text('FPS: ' + frame.frameRate.toFixed(1)); }, layerRef.value.getNode()); anim.start(); }); onUnmounted(() => { if (anim) { anim.stop(); } }); </script>