diff --git a/src/pages/training/components/StabilityEnergyTrack.vue b/src/pages/training/components/StabilityEnergyTrack.vue index 2828a4c..6419b13 100644 --- a/src/pages/training/components/StabilityEnergyTrack.vue +++ b/src/pages/training/components/StabilityEnergyTrack.vue @@ -18,13 +18,15 @@ const props = defineProps({ const WIDTH = 750; const HEIGHT = 228; -const MASK_SCALE = 3; +const MASK_SCALE = 2; const MASK_WIDTH = WIDTH / MASK_SCALE; const MASK_HEIGHT = HEIGHT / MASK_SCALE; const ENTRY_PADDING = 24; const EXIT_PADDING = 32; const MAX_FEATHER = 60; const FULL_TRACK_OFFSET_Y = -3; +const ENTRY_EDGE_BLEND_PERCENT = 5; +const EXIT_EDGE_BLEND_START_PERCENT = 90; const SWEEP_TRACK_RADIUS = 28; const SWEEP_TRACK_FEATHER = 8; const ENTRY_ANIMATION_DURATION = 1000; @@ -32,6 +34,9 @@ const UPDATE_DURATION = 240; const SWEEP_DELAY = 100; const SWEEP_DURATION = 1000; const ASSET_ROOT = "https://static.shelingxingqiu.com/shootmini/static/training-difficulty-design/stability-energy"; +const ASSET_VERSION = "20260922"; +const getAssetSource = (filename) => + `${ASSET_ROOT}/${filename}?v=${ASSET_VERSION}`; const instance = getCurrentInstance(); const canvasId = `stability-energy-${Math.random().toString(36).slice(2, 10)}`; @@ -42,9 +47,9 @@ const normalizedPercent = computed(() => const shouldPlayEntryAnimation = ref(normalizedPercent.value <= 0); const fallbackImageSource = computed( () => - `${ASSET_ROOT}/${ + getAssetSource( shouldPlayEntryAnimation.value ? "progress-full.png" : "progress-empty.png" - }` + ) ); let engine = null; @@ -81,15 +86,25 @@ const smoothStep = (value) => { return amount * amount * (3 - 2 * amount); }; +const enableImageSmoothing = (context) => { + if ("imageSmoothingEnabled" in context) { + context.imageSmoothingEnabled = true; + } + if ("imageSmoothingQuality" in context) { + context.imageSmoothingQuality = "high"; + } +}; + // 复用参考 HTML 的贝塞尔路径,保证能量光带沿靶纸下方的弧线推进。 const sampleTrack = () => { const curves = [ - [280, 26, 226, 31, 121, 49, 125, 82], + // 新素材移除了左上方延伸段,进度从箭头端点开始进入弯道。 + [171, 53, 145, 56, 123, 68, 125, 82], [125, 82, 128, 128, 272, 163, 412, 163], [412, 163, 530, 164, 650, 137, 657, 98], [657, 98, 659, 86, 650, 76, 643, 72], ]; - const points = [{ x: 280, y: 26, distance: 0 }]; + const points = [{ x: 171, y: 53, distance: 0 }]; curves.forEach((curve) => { for (let step = 1; step <= 32; step += 1) { @@ -149,6 +164,7 @@ const createOffscreenCanvas = (canvas, width = WIDTH, height = HEIGHT) => { layerCanvas.height = height; const context = layerCanvas.getContext("2d"); if (!context) throw new Error("Offscreen canvas context unavailable"); + enableImageSmoothing(context); return { canvas: layerCanvas, context }; }; @@ -172,6 +188,7 @@ const loadCanvasImage = (canvas, source) => const createEnergyEngine = async (canvas) => { const context = canvas.getContext("2d"); if (!context) throw new Error("Canvas unavailable"); + enableImageSmoothing(context); canvas.width = WIDTH; canvas.height = HEIGHT; @@ -190,9 +207,9 @@ const createEnergyEngine = async (canvas) => { maskPixels.data.fill(255); const images = await Promise.all([ - loadCanvasImage(canvas, `${ASSET_ROOT}/progress-empty.png`), - loadCanvasImage(canvas, `${ASSET_ROOT}/progress-full.png`), - loadCanvasImage(canvas, `${ASSET_ROOT}/progress-glow.png`), + loadCanvasImage(canvas, getAssetSource("progress-empty.png")), + loadCanvasImage(canvas, getAssetSource("progress-full.png")), + loadCanvasImage(canvas, getAssetSource("progress-glow.png")), ]); const segments = track.slice(1).map((end, index) => { @@ -338,11 +355,20 @@ const createEnergyEngine = async (canvas) => { return; } - // 真实百分比只映射轨道本体,避免 90%~100% 被出口和羽化缓冲吞掉。 + // 主体百分比仍映射轨道本体,头尾仅补偿端点羽化,避免整图与蒙版切换时跳变。 const distance = (progress / 100) * pathLength; + const entryBlend = smoothStep(progress / ENTRY_EDGE_BLEND_PERCENT); + const exitBlend = smoothStep( + (progress - EXIT_EDGE_BLEND_START_PERCENT) / + (100 - EXIT_EDGE_BLEND_START_PERCENT) + ); for (let pixel = 0; pixel < revealStarts.length; pixel += 1) { + const trackAlpha = + smoothStep( + (distance - revealStarts[pixel]) * revealRates[pixel] + ) * entryBlend; maskPixels.data[pixel * 4 + 3] = Math.round( - 255 * smoothStep((distance - revealStarts[pixel]) * revealRates[pixel]) + 255 * (trackAlpha + (1 - trackAlpha) * exitBlend) ); } maskLayer.context.putImageData(maskPixels, 0, 0); @@ -361,12 +387,14 @@ const createEnergyEngine = async (canvas) => { const headBlend = smoothStep(progress / 10) * (1 - smoothStep((progress - 85) / 15)); const position = trackPosition(distance - 16); - const headSize = 72 * (0.55 + 0.45 * headBlend); + const headSize = Math.round(72 * (0.55 + 0.45 * headBlend)); + const headX = Math.round(position.x - headSize / 2); + const headY = Math.round(position.y - headSize / 2); glowLayer.context.clearRect(0, 0, WIDTH, HEIGHT); glowLayer.context.drawImage( images[2], - position.x - headSize / 2, - position.y - headSize / 2, + headX, + headY, headSize, headSize ); @@ -621,6 +649,6 @@ onBeforeUnmount(() => { } .stability-energy-track__image--full { - transform: translateY(-3rpx); + transform: translateY(-2.8rpx); } diff --git a/src/static/training-difficulty-design/stability-energy/progress-empty.png b/src/static/training-difficulty-design/stability-energy/progress-empty.png index 13957cf..4187278 100644 Binary files a/src/static/training-difficulty-design/stability-energy/progress-empty.png and b/src/static/training-difficulty-design/stability-energy/progress-empty.png differ diff --git a/src/static/training-difficulty-design/stability-energy/progress-full.png b/src/static/training-difficulty-design/stability-energy/progress-full.png index bf62d85..8a3d702 100644 Binary files a/src/static/training-difficulty-design/stability-energy/progress-full.png and b/src/static/training-difficulty-design/stability-energy/progress-full.png differ diff --git a/src/static/training-difficulty-design/stability-energy/progress-glow.png b/src/static/training-difficulty-design/stability-energy/progress-glow.png index f11a7df..6aae745 100644 Binary files a/src/static/training-difficulty-design/stability-energy/progress-glow.png and b/src/static/training-difficulty-design/stability-energy/progress-glow.png differ