Skip to content

Photo

对应 ModuleB/src/views/PhotoView.vuesrc/lib/frames.js。按用时推出来它大约 4.50,比 Schedule 还重,不要留到最后。这一块不依赖 JSON,日程卡住时可以先做它。

API 本身分开写了,Photo 页只保留比赛规则。画布见 Canvas,分享和下载见 Web Share

这份练习包里的文件

设计Normal 1080×1080Wide 1600×1080
bands polaroid diagonal neon
panels没有frame_wide_panels.png
geometricframe_geometric.png没有

正式比赛以 ls public/frames 为准,不要背这张表。没有的比例按钮加 disabled,而且当前比例不可用时自动切到有的那个。

切了比例画布却是空的

必须等 decode() 结束再改 canvas.width。题目没有要求处理连点两次、先点的图后到,不用写序号。

js
async function loadFrame() {
  const src = ratio.value === 'wide' ? design.value.wide : design.value.normal
  if (!src) return
  const img = new Image()
  img.src = src
  await img.decode()
  frameImg = img
  redraw()
}

铺不满或变形

cover 是 Math.maxMath.min 会留白。内部像素用 frame 的 naturalWidth/Height,CSS 只写 max-width: 100%; height: auto

js
function drawCover(ctx, img, width, height) {
  const scale = Math.max(width / img.width, height / img.height)
  const w = img.width * scale
  const h = img.height * scale
  ctx.drawImage(img, (width - w) / 2, (height - h) / 2, w, h)
}
function redraw() {
  const width = frameImg.naturalWidth
  const height = frameImg.naturalHeight
  canvas.value.width = width
  canvas.value.height = height
  const ctx = canvas.value.getContext('2d')
  if (userImg) drawCover(ctx, userImg, width, height)
  ctx.drawImage(frameImg, 0, 0, width, height)
}

canvas.width 会清空画布,所以每次都要完整重画。选过一张图后再选同一张,要先把 input.value 清空。

分享和下载

模板里用 @click="onDownload",不要写成 @click="downloadBlob",否则第一个参数会变成鼠标事件。

文件名是 worldskills-shanghai-2026- 加 14 位本地时间。见 考场粘贴

js
async function onShare() {
  const blob = await toBlob()
  const file = new File([blob], fileName(), { type: 'image/png' })
  if (navigator.canShare?.({ files: [file] })) {
    try {
      await navigator.share({
        title: 'WorldSkills Shanghai 2026',
        text: 'My conference photo souvenir!',
        files: [file],
      })
    } catch (err) {
      if (err.name === 'AbortError') {
        hint.value = 'Sharing was not completed. Use Download to save the image.'
        return
      }
      await downloadBlob(blob)
    }
  } else {
    await downloadBlob(blob)
  }
}

一点分享就下载

AbortError 要单独 return。把它当成失败再下载,取消分享也会存文件。

不支持分享时自动下载,控制台不能有红字。revokeObjectURL 不要在 click() 的同一行立刻调用,等一秒。