Khi sử dụng thư viện Konva để vẽ hàng loạt điểm dữ liệu trên canvas kèm theo tính năng kéo thả, người dùng có thể gặp hiện tượng giật/lag nghiêm trọng. Một nguyên nhân ít được chú ý nhưng lại gây ảnh hưởng lớn đến hiệu năng chính là việc sử dụng thuộc tính stroke và strokeWidth.
1. Phân tích nguyên nhân
(1) Tăng độ phức tạp khi render
Việc thêm描边 (stroke) buộc engine phải thực hiện thêm bước vẽ viền cho mỗi hình. Quá trình này tốn kém hơn so với chỉ tô nền (fill), đặc biệt khi strokeWidth lớn hoặc số lượng đối tượng cao.
(2) Kích hoạt offscreen rendering
Konva thường dùng kỹ thuật offscreen rendering để xử lý描边—tức là vẽ trước lên một canvas tạm rồi mới hợp nhất vào canvas chính. Điều này làm tăng cả bộ nhớ lẫn thời gian xử lý, nhất là khi canvas liên tục cập nhật như trong thao tác kéo thả.
(3) Ảnh hưởng đến hit detection
Cơ chế phát hiện va chạm (hit detection) của Konva tính đến vùng描边. Khi strokeWidth lớn, vùng kiểm tra mở rộng, dẫn đến chi phí tính toán tăng đáng kể trong khu vực dày đặc đối tượng.
(4) Giới hạn hiệu năng từ trình duyệt
Trình duyệt phải xử lý nhiều lệnh vẽ hơn khi có描边, bao gồm chống răng cưa và tính toán đường viền phức tạp. Với số lượng lớn phần tử, điều này dễ dẫn đến nghẽn cổ chai hiệu năng.
2. Các phương án tối ưu
(1) Giảm strokeWidth
Sử dụng giá trị nhỏ nhất có thể (ví dụ: strokeWidth: 1) để giảm tải cho GPU/CPU.
const shapeConfig = {
fill: '#ff0000',
stroke: '#000000',
strokeWidth: 1
};
(2) Loại bỏ描边 nếu không cần thiết
Nếu hiệu ứng viền không quan trọng, hãy chỉ dùng fill.
const shapeConfig = {
fill: '#ff0000'
};
(3) Dùng bóng đổ thay vì描边
Thuộc tính shadow thường nhẹ hơn về mặt hiệu năng so với描边.
const shapeConfig = {
fill: '#ff0000',
shadowColor: '#000000',
shadowBlur: 2,
shadowOffsetX: 1,
shadowOffsetY: 1
};
(4) Gộp các hình giống nhau
Kết hợp nhiều hình có cùng kiểu描边 thành một Konva.Path để giảm số lần gọi hàm vẽ.
(5) Sử dụng batchDraw()
Thay vì gọi draw() liên tục khi kéo, hãy dùng batchDraw() để dồn nhiều thay đổi vào một lần render.
stage.batchDraw();
(6) Tắt chế độ vẽ chính xác
Đặt perfectDrawEnabled: false để bỏ qua một số bước render không cần thiết.
const shapeConfig = {
perfectDrawEnabled: false
};
(7) Hạn chế số lượng phần tử
Ẩn hoặc hợp nhất các đối tượng không cần hiển thị đồng thời.
(8) Kích hoạt cache
Chuyển đối tượng thành bitmap tĩnh bằng cache() để tránh render lại mỗi khung hình.
const node = new Konva.Rect({ /* ... */ });
node.cache();
3. Ví dụ minh họa
<template>
<v-stage :config="{ width: 600, height: 400 }">
<v-layer ref="layerRef">
<v-rect
v-for="(item, idx) in items"
:key="idx"
:config="{
x: item.x,
y: item.y,
width: 30,
height: 30,
fill: 'blue',
strokeWidth: 1,
stroke: 'gray',
perfectDrawEnabled: false
}"
/>
</v-layer>
</v-stage>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const items = ref(Array.from({ length: 500 }, (_, i) => ({
x: (i % 20) * 30,
y: Math.floor(i / 20) * 30
})));
onMounted(() => {
// Có thể gọi cache() ở đây nếu cần
});
</script>
Lưu ý: Trong trường hợp kéo toàn bộ nhóm, nên áp dụng batchDraw() trên stage sau mỗi thay đổi vị trí, kết hợp với tắt perfectDrawEnabled và giảm strokeWidth để đạt hiệu suất tối ưu.