Appearance
LED显示屏画布工具(led-screen-canvas)
led-screen-canvas 用于绘制 LED显示屏布局, 包括机箱、面板、像素、围栏等;
- 交互支持:缩放、平移、拖拽、激活 等常用功能;
- 轻量性:组件本身基于
原生canvas+原生js实现(没有任何外部依赖),外部封装为vue3组件,也可以很方便的封装为react或者 直接原生使用; - 独立对象性能:支持 25万(500x500) 独立对象 流畅渲染;
- 规则矩阵性能:支持 16K显示屏像素矩阵(15360 × 8640) 流畅渲染;
安装
使用以下命令安装 led-screen-canvas:
bash
npm install led-screen-canvas
# 或
yarn add led-screen-canvas导入组件
全局导入
在 main.js 中全局引入 led-screen-canvas:
ts
import {createApp} from "vue";
import App from "./App.vue";
import LedScreenCanvas from "led-screen-canvas";
createApp(App).use(LedScreenCanvas).mount("#app");按需导入
在需要的组件中导入 led-screen-canvas:
vue
<script setup lang="ts">
import {LedScreenCanvas} from "led-screen-canvas";
</script>
<template>
<led-screen-canvas/>
</template>操作
鼠标拖拽,即可拖拽元素;鼠标滚轮,即可缩放元素;按住ctrl键+鼠标拖拽,即可平移画布;
基本使用
可拖拽、可激活元素添加到画布中, 并点击设置激活状态;
- 以下是
500x500 = 250000Rectangle物体的渲染演示;
TIP
放大显示文字,点击激活元素,拖转元素时自动将元素置顶;
由于受到前端性能限制,25万 可能是个极限,理论上可以渲染更多的物体,但是渲染效率上可能无法接受;
如果是规则矩阵渲染,可以使用 Matrix 对象,性能上会提高很多;
点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Rectangle } from "led-screen-canvas";
import { ref } from "vue";
const canvasRef = ref();
const onReady = () => {
const rects = [];
const size = 100;
for (let r = 0; r < 500; r++) {
for (let c = 0; c < 500; c++) {
const rect = new Rectangle(c * size, r * size, size, size);
rect.canActive = true;
rect.canMove = true;
rect.text = `Panel(${r},${c})`;
rects.push(rect);
}
}
canvasRef.value.addShapes(rects);
};
</script>
<template>
<a-space direction="vertical" style="width: 100%">
<a-space wrap>
<a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
<a-button type="primary" status="danger" @click="canvasRef.stop()"
>暂停渲染(避免多个画布导致浏览器卡顿)</a-button
>
<a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
<a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
</a-space>
<led-screen-canvas
style="height: 400px; background-color: #dddddd"
ref="canvasRef"
@on-ready="onReady"
@on-click="(shape, e) => shape?.setActive(!shape.isActive(e), e)"
@on-shape-move-start="shape => shape.setZIndexTop()"
/>
</a-space>
</template>
<style scoped lang="less"></style>矩阵渲染
由于矩阵存在明显规律性,因此针对矩阵渲染做了大量优化,可以让矩阵渲染变得极为流畅;
- 以下是
15360 × 8640 = 1.32亿像素矩阵的渲染演示;
TIP
模拟曲面屏像素点最差布局(面板大小100x100, 斜角一半有灯珠,一半没有灯珠的情况);
实际最差布局为全部棋盘格点亮,但实际显示屏布局没有这种极限情况;
点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Matrix, Rectangle } from "led-screen-canvas";
import { ref } from "vue";
const canvasRef = ref();
const onReady = () => {
const [w, h, size] = [15360, 8640, 50];
const matrix = new Matrix(0, 0, w, h, size, size);
matrix.canMove = false;
matrix.canHover = true;
for (let ofx = 0; ofx < w; ofx += 100) {
for (let ofy = 0; ofy < h; ofy += 100) {
for (let y = 0; y < 100; y++) {
for (let x = 0; x <= y; x++) {
const [tx, ty] = [ofx + (100 - x - 1), ofy + y];
matrix.setActiveGrid(true, tx, ty);
}
}
}
}
canvasRef.value.addShapes([matrix]);
};
</script>
<template>
<a-space direction="vertical" style="width: 100%" wrap>
<a-space>
<a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
<a-button type="primary" status="danger" @click="canvasRef.stop()"
>暂停渲染(避免多个画布导致浏览器卡顿)</a-button
>
<a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
<a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
</a-space>
<led-screen-canvas
style="height: 400px; background-color: #dddddd"
ref="canvasRef"
@on-ready="onReady"
@on-click="(shape, e) => shape?.setActive(!shape.isActive(e), e)"
/>
</a-space>
</template>
<style scoped lang="less"></style>多边形渲染和操作
可用于绘制图像围栏等操作;
- 以下是一个可拖拽顶点六边拖拽演示
TIP
本质上是将多个 Rectangle Line 对象添加到画布中,并通过 Polygon 对象进行集中管理;
点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Matrix, Polygon, Rectangle } from "led-screen-canvas";
import { ref } from "vue";
const canvasRef = ref();
const onReady = () => {
// 一个六边形
const polygon = new Polygon(
[
[0, 0],
[100, 0],
[50, 87], // 60度角的顶点
[-50, 87], // 120度角的顶点
[-100, 0], // 180度角的顶点
[-50, -87], // 240度角的顶点
],
4,
15
);
canvasRef.value.addShapes([polygon]);
};
</script>
<template>
<a-space direction="vertical" style="width: 100%" wrap>
<a-space>
<a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
<a-button type="primary" status="danger" @click="canvasRef.stop()"
>暂停渲染(避免多个画布导致浏览器卡顿)</a-button
>
<a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
<a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
</a-space>
<led-screen-canvas
style="height: 400px; background-color: #dddddd"
ref="canvasRef"
@on-ready="onReady"
/>
</a-space>
</template>
<style scoped lang="less"></style>高级用法(自定义形状)
理论上可以实现任意形状的自定义绘制,只需继承 Shape 类,并实现 draw 方法即可;
- 下面是自定义圆形DEMO
TIP
因为LED显示屏没有圆形布局,本插件默认没有圆形,但可以自行实现;
点击展开源码
vue
<script setup lang="ts">
import { BoundingBox, LedScreenCanvas, Matrix, Polygon, Rectangle, Shape } from "led-screen-canvas";
import { ref } from "vue";
import { ShapeType } from "led-screen-canvas/dist/led-screen-canvas/shapes";
import { LedScreenController } from "led-screen-canvas/dist/led-screen-canvas/controller";
const canvasRef = ref();
// 定义圆形对象
class Circle extends Shape {
constructor(
public x: number,
public y: number,
public radius: number,
public borderWidth: number
) {
super("circle" as ShapeType);
}
addToController(controller: LedScreenController): void {
controller.shapes.push(this);
}
clearHover(): void {
this.hover = false;
}
/**
* 实现图形绘制接口
*/
async draw(): Promise<boolean> {
let { ctx, theme, scale } = this.controller;
theme = this.getActiveTheme(theme);
let colors = theme.default;
if (this.active) colors = theme.active;
else if (this.hover) colors = theme.hover;
ctx.beginPath();
const [cx, cy] = this.controller.cvs2win(this.x, this.y);
const radius = this.radius * scale;
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.fillStyle = colors.fillColor;
ctx.strokeStyle = colors.borderColor;
ctx.lineWidth = this.borderWidth * scale;
ctx.fill();
ctx.stroke();
ctx.closePath();
return true;
}
getBoundingBox(): BoundingBox {
return BoundingBox.fromXywh(
this.x - this.radius,
this.y - this.radius,
this.radius * 2,
this.radius * 2
);
}
isActive(e: MouseEvent): boolean {
return this.active;
}
moveTo(x: number, y: number, start: number[], e: MouseEvent): void {
this.x = x;
this.y = y;
}
setActive(active: boolean, e: MouseEvent): void {
this.active = active;
}
toHover(cvsX: number, cvsY: number): void {
const [dx, dy] = [cvsX - this.x, cvsY - this.y];
this.hover = dx * dx + dy * dy < this.radius * this.radius;
}
}
const onReady = () => {
const circle1 = new Circle(100, 100, 50, 5);
circle1.canMove = true;
const circle2 = new Circle(300, 100, 50, 5);
circle2.canMove = true;
canvasRef.value.addShapes([circle1, circle2]);
};
</script>
<template>
<a-space direction="vertical" style="width: 100%" wrap>
<a-space>
<a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
<a-button type="primary" status="danger" @click="canvasRef.stop()"
>暂停渲染(避免多个画布导致浏览器卡顿)</a-button
>
<a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
<a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
</a-space>
<led-screen-canvas
style="height: 400px; background-color: #dddddd"
ref="canvasRef"
@on-ready="onReady"
@on-click="shape => (shape.active = !shape.active)"
@on-shape-move-start="shape => shape.setZIndexTop()"
/>
</a-space>
</template>
<style scoped lang="less"></style>