diff --git a/src/views/iot/device/display.vue b/src/views/iot/device/display.vue index 9560af5..bd2526e 100644 --- a/src/views/iot/device/display.vue +++ b/src/views/iot/device/display.vue @@ -752,7 +752,7 @@ export default { imageSize: 1, // 32x32 imageColor: 0, // 红色(默认) effect: 0, - speed: 4, + speed: 0, stayTime: 5, hAlign: 1, vAlign: 1, @@ -1053,7 +1053,7 @@ export default { imageSize: 1, imageColor: 0, // 红色(默认) effect: 0, - speed: 4, + speed: 0, stayTime: 5, hAlign: 1, vAlign: 1, @@ -1093,7 +1093,7 @@ export default { // 解析动画效果 if (item.anim) { zone.effect = item.anim.typ ? item.anim.typ - 1 : 0; // 动画类型需要减1 - zone.speed = item.anim.spd || 4; // 动画速度 + zone.speed = item.anim.spd || 0; // 动画速度 zone.stayTime = item.anim.pauseT ? this.getStayTimeIndex(item.anim.pauseT) : 5; // 暂停时间索引 } } @@ -1117,7 +1117,7 @@ export default { imageSize: 1, imageColor: 0, // 红色(默认) effect: 0, - speed: 4, + speed: 0, stayTime: 5, hAlign: 1, vAlign: 1, @@ -2231,7 +2231,7 @@ export default { imageSize: 1, imageColor: 0, // 红色(默认) effect: 0, - speed: 4, + speed: 0, stayTime: 5, hAlign: 1, vAlign: 1, @@ -2275,7 +2275,7 @@ export default { imageSize: 1, imageColor: 0, // 红色(默认) effect: 0, - speed: 4, + speed: 0, stayTime: 5, hAlign: 1, vAlign: 1, @@ -2413,7 +2413,7 @@ export default { // 只下发当前节目 const programData = { - del_prog: 0, // 不删除节目 + // del_prog: 0, // 不删除节目 prog_list: [programObj] }; @@ -2517,7 +2517,7 @@ export default { if (zone.playType === 0 && asset.isText) { // 文本类型分区 - const page = this.addProgramPreviewPage[zIdx] || 0; + const page = state && typeof state.currentPage === 'number' ? state.currentPage : 0; const pageLines = asset.pages[page] || []; // 设置字体样式 @@ -2580,7 +2580,17 @@ export default { if (effect === 3 || effect === 4) animOffsetY = state.currentY; } - ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY); + // 连续左移效果(无缝循环) + if (zone.effect === 5 && state) { + let x1 = zoneX + state.currentX; + let x2 = x1 + textWidth; + // 垂直居中 + let y = zoneY + (zoneHeight - lineHeight) / 2; + ctx.fillText(line, x1, y); + ctx.fillText(line, x2, y); + } else { + ctx.fillText(line, startX + (state ? state.currentX : 0), startY + lineIdx * lineHeight + (state ? state.currentY : 0)); + } }); } else if (zone.playType === 1 && asset.isImage) { // 图片类型分区 @@ -2668,124 +2678,124 @@ export default { }); }, prepareAddProgramPreviewAssets() { - const canvas = this.$refs.addProgramPreviewCanvas; - if (!canvas) return; + const canvas = this.$refs.addProgramPreviewCanvas; + if (!canvas) return; - // 动态获取屏幕参数 - let screenW = this.screenParams.width || 32; - let screenH = this.screenParams.height || 64; - // 如果角度为90或270,渲染时宽高互换 - const angle = this.screenParams.angle; - const swapWH = angle === 90 || angle === 270; - if (swapWH) { - [screenW, screenH] = [screenH, screenW]; - } - const renderWidth = 640; // 与实际渲染宽度匹配 - const scale = Math.min(renderWidth / screenW, 240 / screenH); + // 动态获取屏幕参数 + let screenW = this.screenParams.width || 32; + let screenH = this.screenParams.height || 64; + // 如果角度为90或270,渲染时宽高互换 + const angle = this.screenParams.angle; + const swapWH = angle === 90 || angle === 270; + if (swapWH) { + [screenW, screenH] = [screenH, screenW]; + } + const renderWidth = 640; // 与实际渲染宽度匹配 + const scale = Math.min(renderWidth / screenW, 240 / screenH); - this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { - if (zone.playType === 1 && zone.image) { - console.log('加载图片', zone.image); - // 图片类型,异步加载图片 - const img = new window.Image(); - // img.crossOrigin = 'Anonymous'; - const asset = { img, isImage: true, loaded: false }; - img.onload = () => { - asset.loaded = true; - this.drawAddProgramPreview(); - }; - img.onerror = (e) => { - console.error("Preview image failed to load:", e); - }; - img.src = zone.image; - return asset; - } - if (zone.playType !== 0 || !zone.displayText) { - return { pages: [[]], isText: false }; - } + this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { + if (zone.playType === 1 && zone.image) { + // 图片类型,异步加载图片 + const img = new window.Image(); + const asset = { img, isImage: true, loaded: false }; + img.onload = () => { + asset.loaded = true; + this.drawAddProgramPreview(); + }; + img.onerror = (e) => { + console.error("Preview image failed to load:", e); + }; + img.src = zone.image; + return asset; + } + if (zone.playType !== 0 || !zone.displayText) { + return { pages: [[]], isText: false }; + } - // 获取原始字体大小(像素值) - const fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px'); + // 获取原始字体大小(像素值) + const fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px'); - // 计算缩放后的字体大小 - const scaledFontSize = Math.max(10, Math.round(fontSize * scale * 0.8)); // 添加0.8缩放系数 + // 计算缩放后的字体大小 + const scaledFontSize = Math.max(10, Math.round(fontSize * scale * 0.8)); // 添加0.8缩放系数 - const fontFamily = [ - 'SimSun, 宋体, Songti SC, serif', - 'SimHei, 黑体, Heiti SC, sans-serif', - 'KaiTi, 楷体, Kaiti SC, serif' - ]; - const fontWeight = zone.fontBold ? 'bold' : 'normal'; + const fontFamily = [ + 'SimSun, 宋体, Songti SC, serif', + 'SimHei, 黑体, Heiti SC, sans-serif', + 'KaiTi, 楷体, Kaiti SC, serif' + ]; + const fontWeight = zone.fontBold ? 'bold' : 'normal'; - // 创建临时canvas测量文本 - const measureCanvas = document.createElement('canvas'); - const measureCtx = measureCanvas.getContext('2d'); - const fontFamilies2 = [ - 'SimSun, 宋体, Songti SC, serif', - 'SimHei, 黑体, Heiti SC, sans-serif', - 'KaiTi, 楷体, Kaiti SC, serif' - ]; - const enFontMap2 = [ - 'Courier New', 'Arial Black', 'Arial Italic', 'Lucida Console', 'Impact', 'Gothic', 'Arial Narrow', 'Comic Sans MS', 'Brush Script MT', 'Century Gothic', 'Times New Roman' - ]; - let fontFamily2; - if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) { - fontFamily2 = enFontMap2[zone.fontEn] || fontFamilies2[0]; + // 创建临时canvas测量文本 + const measureCanvas = document.createElement('canvas'); + const measureCtx = measureCanvas.getContext('2d'); + const enFontMap = [ + 'Courier New', 'Arial Black', 'Arial Italic', 'Lucida Console', 'Impact', 'Gothic', 'Arial Narrow', 'Comic Sans MS', 'Brush Script MT', 'Century Gothic', 'Times New Roman' + ]; + let fontFamilyUsed; + if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) { + fontFamilyUsed = enFontMap[zone.fontEn] || fontFamily[0]; + } else { + fontFamilyUsed = fontFamily[zone.font] || fontFamily[0]; + } + measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamilyUsed}`; + + // 分行逻辑 - 特殊处理连续左移效果 + let lines = []; + if (zone.effect === 5) { // 连续左移效果 + // 强制不分行,整个文本作为一行 + lines = [zone.displayText]; + } else { + // 正常的分行逻辑 + const zoneRenderWidth = swapWH ? zone.height : zone.width; + const maxWidth = zoneRenderWidth * scale - 4; // 预留4px边距 + let currentLine = ''; + + for (const char of zone.displayText) { + const testLine = currentLine + char; + const metrics = measureCtx.measureText(testLine); + + if (metrics.width > maxWidth) { + if (currentLine) lines.push(currentLine); + currentLine = char; } else { - fontFamily2 = fontFamilies2[zone.font] || fontFamilies2[0]; + currentLine = testLine; } - measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamily2}`; + } - // 分行逻辑 - const zoneRenderWidth = swapWH ? zone.height : zone.width; - const maxWidth = zoneRenderWidth * scale - 4; // 预留4px边距 - let currentLine = ''; - const lines = []; + if (currentLine) lines.push(currentLine); + } - for (const char of zone.displayText) { - const testLine = currentLine + char; - const metrics = measureCtx.measureText(testLine); + // 计算每页最大行数 + const zoneRenderHeight = swapWH ? zone.width : zone.height; + const lineHeight = scaledFontSize * 1.2; + const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight)); - if (metrics.width > maxWidth) { - if (currentLine) lines.push(currentLine); - currentLine = char; - } else { - currentLine = testLine; - } - } + // 分页 + const pages = []; + for (let i = 0; i < lines.length; i += maxLines) { + pages.push(lines.slice(i, i + maxLines)); + } - if (currentLine) lines.push(currentLine); + if (pages.length === 0) pages.push([]); - // 计算每页最大行数 - const zoneRenderHeight = swapWH ? zone.width : zone.height; - const lineHeight = scaledFontSize * 1.2; - const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight)); - - // 分页 - const pages = []; - for (let i = 0; i < lines.length; i += maxLines) { - pages.push(lines.slice(i, i + maxLines)); - } - - if (pages.length === 0) pages.push([]); - - // 新增:计算总宽高和区域宽高,供动画用 - const totalWidth = Math.max(...lines.map(line => measureCtx.measureText(line).width), 0); - const totalHeight = lines.length * lineHeight; - return { - pages, - isText: true, - scaledFontSize, - fontFamily: fontFamily2, - fontWeight, - lineHeight, - totalWidth, - totalHeight, - zoneRenderWidth: zoneRenderWidth * scale, - zoneRenderHeight: zoneRenderHeight * scale - }; - }); - }, + // 计算总宽高和区域宽高,供动画用 + const totalWidth = Math.max(...pages.flat().map(line => measureCtx.measureText(line).width), 0); + const totalHeight = Math.min(lines.length, maxLines) * lineHeight; + + return { + pages, + isText: true, + scaledFontSize, + fontFamily: fontFamilyUsed, + fontWeight, + lineHeight, + totalWidth, + totalHeight, + zoneRenderWidth: (swapWH ? zone.height : zone.width) * scale, + zoneRenderHeight: (swapWH ? zone.width : zone.height) * scale + }; + }); +}, resetAddProgramPreviewPage() { this.addProgramPreviewPage = this.addProgramForm.zones.map(() => 0); }, @@ -2881,292 +2891,118 @@ export default { this.addProgramPreviewAnimFrame = requestAnimationFrame(loop); }, updateAddProgramPreviewAnimState() { - // 动画主循环,更新每个分区的动画状态 - const renderWidth = 640; - const renderHeight = 240; - // const scale = Math.min(renderWidth / 32, renderHeight / 64); - // 使用与 prepareAddProgramPreviewAssets 中相同的 scale 计算方式 - let screenW = this.screenParams.width || 32; - let screenH = this.screenParams.height || 64; - const angle = this.screenParams.angle; - const swapWH = angle === 90 || angle === 270; - if (swapWH) { - [screenW, screenH] = [screenH, screenW]; - } - const scale = Math.min(renderWidth / screenW, renderHeight / screenH); + // 动画主循环,更新每个分区的动画状态 + const renderWidth = 640; + const renderHeight = 240; + let screenW = this.screenParams.width || 32; + let screenH = this.screenParams.height || 64; + const angle = this.screenParams.angle; + const swapWH = angle === 90 || angle === 270; + if (swapWH) { + [screenW, screenH] = [screenH, screenW]; + } + const scale = Math.min(renderWidth / screenW, renderHeight / screenH); + this.addProgramForm.zones.forEach((zone, idx) => { + const asset = this.addProgramPreviewAssets[idx]; + const state = this.addProgramPreviewAnimState[idx]; - this.addProgramForm.zones.forEach((zone, idx) => { - const asset = this.addProgramPreviewAssets[idx]; - const state = this.addProgramPreviewAnimState[idx]; + if (!asset || !state || !asset.isText) return; - if (!asset || !state || !asset.isText) return; + const effect = zone.effect; + const page = this.addProgramPreviewPage[idx] || 0; + const pageLines = asset.pages[page] || []; + const lineHeight = asset.lineHeight; + const totalHeight = asset.totalHeight; + const totalWidth = asset.totalWidth; - const effect = zone.effect; - const page = this.addProgramPreviewPage[idx] || 0; - const pageLines = asset.pages[page] || []; - const lineHeight = asset.lineHeight; - // const totalHeight = pageLines.length * lineHeight; // 原始计算 - // const totalWidth = Math.max(...pageLines.map(line => measureCtx.measureText(line).width), 0); // 原始计算 - // 使用 prepareAddProgramPreviewAssets 中预先计算好的值 - const totalHeight = asset.totalHeight; - const totalWidth = asset.totalWidth; + // 动画速度 + const speedMap = [1, 2, 3, 4, 5]; + const animSpeed = (speedMap[zone.speed] || 3) * scale * 0.5; // 像素/帧 + // 区域宽高 (使用预先计算并缩放的值) + const zoneWidth = asset.zoneRenderWidth; + const zoneHeight = asset.zoneRenderHeight; - // 动画速度 - const speedMap = [1, 2, 3, 4, 5]; - const animSpeed = (speedMap[zone.speed] || 3) * scale * 0.5; // 像素/帧 + // 暂停时间 (毫秒) + const pauseMs = this.getPauseTime(zone.stayTime); - // 区域宽高 (使用预先计算并缩放的值) - const zoneWidth = asset.zoneRenderWidth; - const zoneHeight = asset.zoneRenderHeight; + // 确保初始状态值存在 + if (typeof state.isPausing !== 'boolean') state.isPausing = false; + if (typeof state.pauseStart !== 'number') state.pauseStart = 0; + if (typeof state.pausePhase !== 'string') state.pausePhase = 'start'; // 'start' | 'move' - // 暂停时间 (毫秒) - const pauseMs = this.getPauseTime(zone.stayTime); - - // 确保初始状态值存在 - if (typeof state.isPausing !== 'boolean') state.isPausing = false; - if (typeof state.pauseStart !== 'number') state.pauseStart = 0; - if (typeof state.pausePhase !== 'string') state.pausePhase = 'start'; // 'start' | 'move' - - - switch (effect) { - case 1: // 左移 - // 确保初始 state.currentX 值存在 - if (typeof state.currentX !== 'number') state.currentX = 0; - // 确保初始 pausePhase 值存在 - if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move' - - // 初始位置停留阶段 - if (!state.isPausing && state.pausePhase === 'start') { - state.isPausing = true; - state.pauseStart = Date.now(); - } - - // 检查初始停留是否结束 - if (state.isPausing && state.pausePhase === 'start') { - if (Date.now() - state.pauseStart >= pauseMs) { - state.isPausing = false; - state.pausePhase = 'move'; - } - // 移动阶段 - } else if (!state.isPausing && state.pausePhase === 'move') { - // --- 强制多移动的逻辑 --- - // !!! 核心修改点 !!! - // 原判断: if (state.currentX + totalWidth > 0) - // 新判断: 计算从起始位置(0)到当前位置已经移动的距离 (-state.currentX) - // 并与一个我们认为足够"移出"的距离进行比较 - const pixelsMoved = -state.currentX; // 从 X=0 开始,向左移动了多远 - const distanceToTriggerReset = zoneWidth + 100; // <--- 关键:移出区域宽度 + 额外距离 - // 例如:区域宽 32 + 额外 100 = 132 - // 你可以调整这个 100 为你觉得合适的值 - - if (pixelsMoved < distanceToTriggerReset) { - // 移动距离还不够,继续移动 - state.currentX -= animSpeed; - } else { - // 移动距离足够了(强制认为已经移出),立即重置 - // console.log(`Zone ${idx} - Left Move: Forcefully reset after moving ${pixelsMoved.toFixed(2)} pixels.`); // 调试信息 - state.currentX = 0; // 回到起始位置 - state.pausePhase = 'start'; // 回到初始停留阶段 - } - // --- 强制多移动逻辑结束 --- - } - // Y轴不移动 - state.currentY = 0; - break; - - - case 2: // 右移 - // 确保初始 state.currentX 值存在 - if (typeof state.currentX !== 'number') state.currentX = 0; // 通常从左侧开始向右移 - // 确保初始 pausePhase 值存在 - if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move' - - // 初始位置停留阶段 - if (!state.isPausing && state.pausePhase === 'start') { - state.isPausing = true; - state.pauseStart = Date.now(); - } - - // 检查初始停留是否结束 - if (state.isPausing && state.pausePhase === 'start') { - if (Date.now() - state.pauseStart >= pauseMs) { - state.isPausing = false; - state.pausePhase = 'move'; - } - // 移动阶段 - } else if (!state.isPausing && state.pausePhase === 'move') { - // --- 修正后的逻辑 --- - // 判断是否还可以继续向右移动 - // 条件:当前文本左边缘距离完全移出右边界还有距离 (currentX < zoneWidth) - if (state.currentX < zoneWidth) { - state.currentX += animSpeed; - // 可选:边界检查 - // if (state.currentX > zoneWidth) { - // state.currentX = zoneWidth; // 精确设置到边界 - // } - } else { - // 内容完全移出右侧,立即重置,准备下一轮 - // 不再进入 'end' 停留阶段 - state.currentX = 0; // 回到左侧起始位置 - state.pausePhase = 'start'; // 回到初始停留阶段 - } - // --- 修正结束 --- - } - // Y轴不移动 - state.currentY = 0; - break; - - case 3: // 上移 - // 确保初始 state.currentY 值存在 - if (typeof state.currentY !== 'number') state.currentY = 0; - // 确保初始 pausePhase 值存在 - if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move' - - // 初始位置停留阶段 - if (!state.isPausing && state.pausePhase === 'start') { - state.isPausing = true; - state.pauseStart = Date.now(); - } - - // 检查初始停留是否结束 - if (state.isPausing && state.pausePhase === 'start') { - if (Date.now() - state.pauseStart >= pauseMs) { - state.isPausing = false; - state.pausePhase = 'move'; - } - // 移动阶段 - } else if (!state.isPausing && state.pausePhase === 'move') { - // --- 修正后的逻辑 --- - // 判断是否还可以继续向上移动 - // 条件:当前文本底边距离完全移出顶边还有距离 (currentY + totalHeight > 0) - if (state.currentY + totalHeight > 0) { - state.currentY -= animSpeed; - // 可选:边界检查 - // if (state.currentY + totalHeight < 0) { - // state.currentY = -totalHeight; - // } - } else { - // 内容完全移出顶部,立即重置,准备下一轮 - // 不再进入 'end' 停留阶段 - state.currentY = 0; // 回到顶部起始位置 - state.pausePhase = 'start'; // 回到初始停留阶段 - } - // --- 修正结束 --- - } - // X轴不移动 - state.currentX = 0; - break; - - case 4: // 下移 - // 确保初始 state.currentY 值存在 - if (typeof state.currentY !== 'number') state.currentY = 0; // 通常从顶部开始向下移 - // 确保初始 pausePhase 值存在 - if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move' - - // 初始位置停留阶段 - if (!state.isPausing && state.pausePhase === 'start') { - state.isPausing = true; - state.pauseStart = Date.now(); - } - - // 检查初始停留是否结束 - if (state.isPausing && state.pausePhase === 'start') { - if (Date.now() - state.pauseStart >= pauseMs) { - state.isPausing = false; - state.pausePhase = 'move'; - } - // 移动阶段 - } else if (!state.isPausing && state.pausePhase === 'move') { - // --- 修正后的逻辑 --- - // 判断是否还可以继续向下移动 - // 条件:当前文本顶边距离完全移出底边还有距离 (currentY < zoneHeight) - if (state.currentY < zoneHeight) { - state.currentY += animSpeed; - // 可选:边界检查 - // if (state.currentY > zoneHeight) { - // state.currentY = zoneHeight; - // } - } else { - // 内容完全移出底部,立即重置,准备下一轮 - // 不再进入 'end' 停留阶段 - state.currentY = 0; // 回到顶部起始位置 - state.pausePhase = 'start'; // 回到初始停留阶段 - } - // --- 修正结束 --- - } - // X轴不移动 - state.currentX = 0; - break; - - case 5: // 连续左移 (立即显示 -> 左移 -> 立即显示 -> ...) - // 确保初始 state.currentX 值存在 - if (typeof state.currentX !== 'number') state.currentX = 0; - // 确保初始 pausePhase 值存在 - if (!state.pausePhase) state.pausePhase = 'start'; // 'start' | 'move' - - // 初始立即显示阶段 (或一轮结束后的显示阶段) - if (!state.isPausing && state.pausePhase === 'start') { - // 立即显示阶段,不移动,停留 pauseMs 时间 - state.isPausing = true; - state.pauseStart = Date.now(); - } - - // 检查立即显示阶段是否结束 - if (state.isPausing && state.pausePhase === 'start') { - if (Date.now() - state.pauseStart >= pauseMs) { - state.isPausing = false; - state.pausePhase = 'move'; // 进入移动阶段 - } - // 移动阶段 (左移) - } else if (!state.isPausing && state.pausePhase === 'move') { - // --- 修正后的逻辑 (连续左移的移动逻辑与普通左移一致) --- - // 判断是否还可以继续向左移动 - if (state.currentX > -totalWidth) { - state.currentX -= animSpeed; - // 可选边界检查 - // if (state.currentX < -totalWidth) { - // state.currentX = -totalWidth; - // } - } else { - // 内容完全移出,进入下一个立即显示阶段 - // 连续左移的效果是在这里停留 pauseMs 后再显示下一轮 - state.isPausing = true; - state.pauseStart = Date.now(); - state.currentX = 0; // 回到起始位置显示 - state.pausePhase = 'start'; // 回到 'start' 阶段进行显示停留 - } - // --- 修正结束 --- - } - state.currentY = 0; // Y轴不移动 - break; - - case 6: // 闪烁换页 - // 由自动翻页定时器控制,此处不处理动画位移 - state.currentX = 0; - state.currentY = 0; - break; - - default: // 立即显示 (effect === 0) 或其他未知效果 - state.currentX = 0; - state.currentY = 0; - // 可以考虑添加立即显示的停留逻辑,如果需要的话 - // if (!state.isPausing && state.pausePhase === 'start') { - // state.isPausing = true; - // state.pauseStart = Date.now(); - // } - // if (state.isPausing && state.pausePhase === 'start') { - // if (Date.now() - state.pauseStart >= pauseMs) { - // state.isPausing = false; - // state.pausePhase = 'end'; // 或者重置为 'start' - // } - // } - // state.pausePhase = 'start'; // 重置状态以便下次循环 - break; + switch (effect) { + case 1: // 左移 + if (typeof state.currentX !== 'number') state.currentX = zoneWidth; + state.currentX -= animSpeed; + if (state.currentX < -totalWidth) { + if (asset.pages.length > 1) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + } + state.currentX = zoneWidth; } - }); - }, + state.currentY = 0; + break; + case 2: // 右移 + if (typeof state.currentX !== 'number') state.currentX = -totalWidth; + state.currentX += animSpeed; + if (state.currentX > zoneWidth) { + if (asset.pages.length > 1) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + } + state.currentX = -totalWidth; + } + state.currentY = 0; + break; + case 3: // 上移 + if (typeof state.currentY !== 'number') state.currentY = zoneHeight; + state.currentY -= animSpeed; + if (state.currentY < -totalHeight) { + if (asset.pages.length > 1) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + } + state.currentY = zoneHeight; + } + state.currentX = 0; + break; + case 4: // 下移 + if (typeof state.currentY !== 'number') state.currentY = -totalHeight; + state.currentY += animSpeed; + if (state.currentY > zoneHeight) { + if (asset.pages.length > 1) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + } + state.currentY = -totalHeight; + } + state.currentX = 0; + break; + case 5: // 连续左移 (跑马灯效果) + // 确保初始位置在分区右侧外部 + if (typeof state.currentX !== 'number') state.currentX = zoneWidth; + // 向左移动文本 + state.currentX -= animSpeed; + // 当文本完全移出左侧边界时,重置到右侧重新开始(无缝循环) + if (state.currentX < -totalWidth) { + state.currentX += totalWidth; + } + // Y轴居中显示 + state.currentY = (zoneHeight - totalHeight) / 2; + break; + + case 6: // 闪烁换页 + // 由自动翻页定时器控制,此处不处理动画位移 + state.currentX = 0; + state.currentY = 0; + break; + + default: // 立即显示 (effect === 0) 或其他未知效果 + state.currentX = 0; + state.currentY = 0; + break; + } + }); +}, measureTextWidth(text, asset) { // 用于动画宽度测量 const canvas = document.createElement('canvas');