feat(editor-drawers): 为断连的自动门点和门区域添加脉冲动画效果,提升离线状态的视觉反馈和用户体验
This commit is contained in:
parent
97bbd47e3c
commit
7dba81550e
@ -12,6 +12,159 @@ __doorImgOpen.src = (doorOpenUrl as unknown as string) || '';
|
||||
const __doorImgClosed = new Image();
|
||||
__doorImgClosed.src = (doorClosedUrl as unknown as string) || '';
|
||||
|
||||
/**
|
||||
* 绘制断连图标遮罩
|
||||
* @param ctx Canvas上下文
|
||||
* @param cx 中心X坐标
|
||||
* @param cy 中心Y坐标
|
||||
* @param size 图标大小
|
||||
* @param opacity 透明度
|
||||
*/
|
||||
function drawDisconnectedIcon(ctx: CanvasRenderingContext2D, cx: number, cy: number, size: number, opacity: number): void {
|
||||
ctx.save();
|
||||
|
||||
// 绘制WiFi断连图标
|
||||
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity * 0.8})`;
|
||||
ctx.fillStyle = `rgba(255, 255, 255, ${opacity * 0.8})`;
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.lineCap = 'round';
|
||||
|
||||
const radius = size / 2;
|
||||
const baseY = cy - radius * 0.3;
|
||||
|
||||
// 绘制三层WiFi信号(从外到内)
|
||||
for (let i = 2; i >= 0; i--) {
|
||||
const r = radius * (0.2 + i * 0.25);
|
||||
const startAngle = -Math.PI * 0.75;
|
||||
const endAngle = -Math.PI * 0.25;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, baseY, r, startAngle, endAngle);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 绘制删除线表示断连
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx - radius * 0.6, baseY - radius * 0.4);
|
||||
ctx.lineTo(cx + radius * 0.6, baseY + radius * 0.4);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制自动门点断连状态的脉冲效果
|
||||
* @param ctx Canvas上下文
|
||||
* @param pen 画笔对象
|
||||
* @param time 当前时间戳(毫秒)
|
||||
*/
|
||||
function drawAutoDoorDisconnectedPulse(ctx: CanvasRenderingContext2D, pen: MapPen, time: number): void {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
const base = Math.min(w, h);
|
||||
const padding = Math.max(2, Math.min(10, base * 0.2));
|
||||
|
||||
// 使用平滑的正弦函数计算脉冲强度 (0.4 - 1.0)
|
||||
// 使用更大的周期让变化更平缓,并添加相位偏移以避免突然变化
|
||||
const pulseIntensity = 0.7 + 0.3 * Math.sin(time / 2500);
|
||||
|
||||
// 外层发光效果
|
||||
const glowGradient = ctx.createRadialGradient(
|
||||
x + w/2, y + h/2, 0,
|
||||
x + w/2, y + h/2, base * 0.8
|
||||
);
|
||||
glowGradient.addColorStop(0, `rgba(255, 149, 0, ${pulseIntensity * 0.3})`);
|
||||
glowGradient.addColorStop(0.5, `rgba(255, 149, 0, ${pulseIntensity * 0.15})`);
|
||||
glowGradient.addColorStop(1, 'rgba(255, 149, 0, 0)');
|
||||
|
||||
ctx.fillStyle = glowGradient;
|
||||
ctx.fillRect(x - padding * 2, y - padding * 2, w + padding * 4, h + padding * 4);
|
||||
|
||||
// 主体渐变光圈
|
||||
const mainGradient = ctx.createLinearGradient(x, y, x + w, y + h);
|
||||
mainGradient.addColorStop(0, `rgba(255, 149, 0, ${pulseIntensity * 0.8})`);
|
||||
mainGradient.addColorStop(0.5, `rgba(244, 81, 30, ${pulseIntensity * 0.85})`);
|
||||
mainGradient.addColorStop(1, `rgba(220, 38, 38, ${pulseIntensity * 0.9})`);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x - padding, y - padding, w + padding * 2, h + padding * 2, 4);
|
||||
ctx.fillStyle = mainGradient;
|
||||
ctx.fill();
|
||||
|
||||
// 内边框高光
|
||||
ctx.strokeStyle = `rgba(255, 255, 255, ${pulseIntensity * 0.3})`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x - padding + 1, y - padding + 1, w + padding * 2 - 2, h + padding * 2 - 2, 3);
|
||||
ctx.stroke();
|
||||
|
||||
// 断连图标遮罩
|
||||
drawDisconnectedIcon(ctx, x + w/2, y + h/2, base * 0.3, pulseIntensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制门区域断连状态的脉冲效果
|
||||
* @param ctx Canvas上下文
|
||||
* @param pen 画笔对象
|
||||
* @param time 当前时间戳(毫秒)
|
||||
*/
|
||||
function drawDoorAreaDisconnectedPulse(ctx: CanvasRenderingContext2D, pen: MapPen, time: number): void {
|
||||
const { x = 0, y = 0, width: w = 0, height: h = 0 } = pen.calculative?.worldRect ?? {};
|
||||
|
||||
// 使用平滑的正弦函数计算脉冲强度 (0.4 - 1.0)
|
||||
// 与自动门点保持一致的节奏,但略微错开相位
|
||||
const pulseIntensity = 0.7 + 0.3 * Math.sin(time / 2500 + Math.PI / 4);
|
||||
|
||||
// 外层脉冲光圈
|
||||
const glowPadding = Math.max(4, Math.min(w, h) * 0.05);
|
||||
const glowGradient = ctx.createRadialGradient(
|
||||
x + w/2, y + h/2, 0,
|
||||
x + w/2, y + h/2, Math.max(w, h) * 0.6
|
||||
);
|
||||
glowGradient.addColorStop(0, `rgba(239, 68, 68, ${pulseIntensity * 0.4})`);
|
||||
glowGradient.addColorStop(0.6, `rgba(239, 68, 68, ${pulseIntensity * 0.2})`);
|
||||
glowGradient.addColorStop(1, 'rgba(239, 68, 68, 0)');
|
||||
|
||||
ctx.fillStyle = glowGradient;
|
||||
ctx.fillRect(x - glowPadding, y - glowPadding, w + glowPadding * 2, h + glowPadding * 2);
|
||||
|
||||
// 主体边框(加粗且带脉冲效果)
|
||||
ctx.strokeStyle = `rgba(239, 68, 68, ${0.6 + pulseIntensity * 0.4})`;
|
||||
ctx.lineWidth = Math.max(3, Math.min(w, h) * 0.02);
|
||||
ctx.setLineDash([]);
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x, y, w, h, 4);
|
||||
ctx.stroke();
|
||||
|
||||
// 内边框高光
|
||||
ctx.strokeStyle = `rgba(255, 255, 255, ${pulseIntensity * 0.2})`;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x + 2, y + 2, w - 4, h - 4, 3);
|
||||
ctx.stroke();
|
||||
|
||||
// 显示断连状态的图标(关门状态)
|
||||
if (__doorImgClosed && __doorImgClosed.complete) {
|
||||
const iconPadding = Math.max(2, Math.min(w, h) * 0.02);
|
||||
const availW = Math.max(0, w - iconPadding * 2);
|
||||
const availH = Math.max(0, h - iconPadding * 2);
|
||||
const ratio = __doorImgClosed.naturalWidth > 0 && __doorImgClosed.naturalHeight > 0 ?
|
||||
__doorImgClosed.naturalWidth / __doorImgClosed.naturalHeight : 1;
|
||||
let drawW = availW;
|
||||
let drawH = drawW / ratio;
|
||||
if (drawH > availH) {
|
||||
drawH = availH;
|
||||
drawW = drawH * ratio;
|
||||
}
|
||||
const dx = x + (w - drawW) / 2;
|
||||
const dy = y + (h - drawH) / 2;
|
||||
|
||||
// 使用平滑的脉冲透明度效果 (0.8 - 1.0)
|
||||
ctx.globalAlpha = 0.85 + 0.15 * Math.sin(time / 2500 + Math.PI / 6);
|
||||
ctx.drawImage(__doorImgClosed, dx, dy, drawW, drawH);
|
||||
ctx.globalAlpha = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
export function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
|
||||
@ -21,9 +174,16 @@ export function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 自动门点:根据连接与开关状态绘制矩形光圈(无边框)
|
||||
// 自动门点:根据连接与开关状态绘制光圈效果
|
||||
if (type === MapPointType.自动门点 && pointActive) {
|
||||
// 让光圈随点位尺寸等比缩放,避免缩放画布时视觉上变大
|
||||
if (isConnected === false) {
|
||||
// 未连接:使用新的脉冲效果
|
||||
const currentTime = performance.now();
|
||||
drawAutoDoorDisconnectedPulse(ctx, pen, currentTime);
|
||||
ctx.restore(); // 提前返回,不执行后续渲染
|
||||
return;
|
||||
} else {
|
||||
// 已连接:使用原有的光圈效果,但优化颜色和样式
|
||||
const base = Math.min(w, h);
|
||||
const padding = Math.max(2, Math.min(10, base * 0.2));
|
||||
const rx = x - padding;
|
||||
@ -35,21 +195,31 @@ export function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(rx, ry, rw, rh, r);
|
||||
|
||||
if (isConnected === false) {
|
||||
// 未连接:深红色实心,不描边
|
||||
ctx.fillStyle = colorConfig.getColor('autoDoor.strokeClosed') || '#fe5a5ae0';
|
||||
} else {
|
||||
// 已连接:根据门状态显示颜色(0=关门-浅红,1=开门-蓝色)
|
||||
// 优化后的颜色方案
|
||||
if (doorStatus === 0) {
|
||||
ctx.fillStyle = colorConfig.getColor('autoDoor.fillClosed') || '#cddc39';
|
||||
// 关门:柔和的橙色渐变
|
||||
const closedGradient = ctx.createLinearGradient(x, y, x + w, y + h);
|
||||
closedGradient.addColorStop(0, 'rgba(255, 193, 7, 0.6)');
|
||||
closedGradient.addColorStop(1, 'rgba(255, 152, 0, 0.8)');
|
||||
ctx.fillStyle = closedGradient;
|
||||
} else {
|
||||
ctx.fillStyle = colorConfig.getColor('autoDoor.fillOpen') || '#1890FF';
|
||||
}
|
||||
// 开门:明亮的蓝色渐变
|
||||
const openGradient = ctx.createLinearGradient(x, y, x + w, y + h);
|
||||
openGradient.addColorStop(0, 'rgba(24, 144, 255, 0.6)');
|
||||
openGradient.addColorStop(1, 'rgba(19, 106, 207, 0.8)');
|
||||
ctx.fillStyle = openGradient;
|
||||
}
|
||||
ctx.fill();
|
||||
|
||||
// 添加细微的边框高光
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
|
||||
// 重置路径,避免后续对点位边框的 stroke 影响到光圈路径
|
||||
ctx.beginPath();
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MapPointType.普通点:
|
||||
@ -297,23 +467,37 @@ export function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
if (!finalStrokeColor) {
|
||||
finalStrokeColor = colorConfig.getColor('area.strokeActive') || '#8C8C8C';
|
||||
}
|
||||
// 门区域断连:加粗、红色(不闪烁)
|
||||
// 门区域断连处理
|
||||
if ((type as any) === DOOR_AREA_TYPE) {
|
||||
const isConnected = (pen.area as any)?.isConnected;
|
||||
if (isConnected === false) {
|
||||
finalStrokeColor = '#ff4d4f';
|
||||
ctx.lineWidth = Math.max(borderWidth * 2, 4);
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
}
|
||||
// 使用新的脉冲效果
|
||||
const currentTime = performance.now();
|
||||
drawDoorAreaDisconnectedPulse(ctx, pen, currentTime);
|
||||
|
||||
// 绘制基本边框(作为备用,确保兼容性)
|
||||
ctx.strokeStyle = 'rgba(239, 68, 68, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(x, y, w, h);
|
||||
} else {
|
||||
// 正常连接状态:使用标准边框
|
||||
ctx.strokeStyle = finalStrokeColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
} else {
|
||||
// 非门区域:使用标准边框
|
||||
ctx.strokeStyle = finalStrokeColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 门区域图标背景:根据设备连接与开关状态绘制淡化图标
|
||||
if ((type as any) === DOOR_AREA_TYPE) {
|
||||
const isConnected = (pen.area as any)?.isConnected;
|
||||
const doorStatus = (pen.area as any)?.doorStatus;
|
||||
const img = isConnected === false ? __doorImgClosed : doorStatus === 1 ? __doorImgOpen : __doorImgClosed;
|
||||
|
||||
// 只有连接状态才绘制常规图标(断连状态已在脉冲效果中处理)
|
||||
if (isConnected !== false) {
|
||||
const img = doorStatus === 1 ? __doorImgOpen : __doorImgClosed;
|
||||
if (img && img.complete) {
|
||||
const padding = Math.max(2, Math.min(10, Math.min(w, h) * 0.02));
|
||||
const availW = Math.max(0, w - padding * 2);
|
||||
@ -331,6 +515,7 @@ export function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.drawImage(img, dx, dy, drawW, drawH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 描述区渲染文字
|
||||
if (type === MapAreaType.描述区 && desc) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user