[{"data":1,"prerenderedAt":58},["ShallowReactive",2],{"$1lbWB9a43m":3},[4,17,27,35,42,49],{"title":5,"description":5,"path":6,"id":7,"date":8,"tags":9,"lang":14,"rawbody":15,"draft":16},"uniapp小程序 LivePhoto(实况图片)实现详解","\u002Fposts\u002Funi-ui-plus-livephoto","posts\u002Fposts\u002Funi-ui-plus-LivePhoto.md","2025-08-25 22:00:00",[10,11,12,13],"Vue","uni-app","小程序","LivePhoto","zh-cn","---\ntitle: uniapp小程序 LivePhoto(实况图片)实现详解\ndate: 2025-08-25 22:00:00\ndescription: uniapp小程序 LivePhoto(实况图片)实现详解\ntags:\n  - Vue\n  - uni-app\n  - 小程序\n  - LivePhoto\nlang: zh-cn\n---\n\n## 前言\n\nLivePhoto（实况照片）是苹果推出的一项创新功能，它能将静态照片与短视频结合，通过长按图片来播放对应的视频内容，为用户带来更加生动的视觉体验。虽然苹果官方提供了 [LivePhotoKit](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Flivephotoskitjs) 库来在 Web 端实现这一功能，但在小程序环境中，由于平台限制无法直接使用该库，虽然前段时间官方推出了朋友圈实况的预览实现，但是相关控件目前并未在小程序端开放。\n\n本文将详细介绍如何在 uni-app 框架下实现一个完整的 LivePhoto 组件，让小程序也能拥有类似 iOS 的实况图片功能。\n\n## 演示\n\n![1.gif](https:\u002F\u002Fi-blog.csdnimg.cn\u002Fdirect\u002F728a4788111c4471b47d3ec15c8f68de.gif)\n\n## 技术挑战\n\n在小程序中实现 LivePhoto 功能面临以下几个主要挑战：\n\n1.  **平台限制**：小程序无法直接使用 `livephotoskitjs` 库\n2.  **视频控制**：需要精确控制视频的播放、暂停和重置\n3.  **交互体验**：实现流畅的长按交互和视觉过渡效果\n4.  **性能优化**：视频预加载和内存管理\n5.  **跨平台兼容**：确保在不同小程序平台上的一致性\n\n## 核心设计思路\n\n### 1. 双层架构\n\nLivePhoto 本质上是静态图片和视频的叠加组合：\n\n```vue\n\u003Ctemplate>\n  \u003Cview class=\"up-live-photo\">\n    \u003C!-- 静态图片层 - 默认显示 -->\n    \u003Cview class=\"up-live-photo__image-layer\">\n      \u003Cup-image :src=\"props.src\" \u002F>\n    \u003C\u002Fview>\n    \n    \u003C!-- 视频层 - 交互时显示 -->\n    \u003Cview class=\"up-live-photo__video-layer\">\n      \u003Cvideo :src=\"props.videoSrc\" \u002F>\n    \u003C\u002Fview>\n  \u003C\u002Fview>\n\u003C\u002Ftemplate>\n```\n\n### 2. 状态管理\n\n通过响应式状态控制组件行为：\n\n```typescript\nconst isPressed = ref(false)        \u002F\u002F 是否正在长按\nconst isVideoPlaying = ref(false)   \u002F\u002F 视频是否播放中\nconst isTransitioning = ref(false)  \u002F\u002F 是否在过渡动画中\n```\n\n## 详细实现\n\n### 1. 核心交互逻辑\n\n#### 长按控制\n\n实现精确的长按交互控制：\n\n```typescript\n\u002F\u002F 长按开始 - 播放视频\nfunction onLongPressStart() {\n  isPressed.value = true\n  \n  \u002F\u002F 振动反馈\n  uni.vibrateShort({ type: 'light' })\n  \n  \u002F\u002F 播放视频\n  const videoCtx = getVideoContext()\n  videoCtx.play()\n  isVideoPlaying.value = true\n}\n\n\u002F\u002F 长按结束 - 暂停视频\nfunction onLongPressEnd() {\n  isPressed.value = false\n  isVideoPlaying.value = false\n  \n  const videoCtx = getVideoContext()\n  videoCtx.pause()\n  videoCtx.seek(0)  \u002F\u002F 重置到开始位置\n}\n```\n\n#### 视频上下文管理\n\n使用 uni-app 的 `createVideoContext` API 精确控制视频：\n\n```typescript\nfunction getVideoContext() {\n  const videoId = `live-photo-video-${componentId.value}`\n  return uni.createVideoContext(videoId, instance)\n}\n```\n\n````\n\n### 2. 视觉过渡效果\n\n通过 CSS 过渡实现流畅的视觉效果：\n\n```scss\n.up-live-photo__image-layer {\n  opacity: 1;\n  transform: scale(1);\n  \n  &--hidden {\n    opacity: 0;\n    transform: scale(0.98);\n  }\n}\n\n.up-live-photo__video-layer {\n  opacity: 0;\n  transform: scale(1.02);\n  \n  &--visible {\n    opacity: 1;\n    transform: scale(1.05);\n  }\n}\n````\n\n### 3. Live 指示器设计\n\n使用纯 CSS 实现 iOS 风格的 LIVE 指示器：\n\n```scss\n.up-live-photo__indicator {\n  position: absolute;\n  background: rgba(0, 0, 0, 0.6);\n  backdrop-filter: blur(20rpx);\n  border-radius: 24rpx;\n  \n  &__indicator-text {\n    color: #ffffff;\n    font-size: 24rpx;\n    font-weight: 600;\n    letter-spacing: 1rpx;\n  }\n}\n```\n\n### 4. 性能优化策略\n\n#### 懒加载机制\n\n视频只在需要时才加载，避免不必要的资源消耗：\n\n```typescript\nfunction startVideoLoad() {\n  if (isVideoLoading.value || isVideoLoaded.value) return\n  \n  isVideoLoading.value = true\n  \u002F\u002F 模拟进度增长，直到视频真正加载完成\n  \u002F\u002F ...\n}\n```\n\n#### 内存管理\n\n组件卸载时清理视频资源：\n\n```typescript\nonUnmounted(() => {\n  const videoCtx = getVideoContext()\n  if (videoCtx) {\n    videoCtx.pause()\n  }\n})\n```\n\n## 使用示例\n\n```vue\n\u003Ctemplate>\n  \u003Cup-live-photo\n    video-src=\"https:\u002F\u002Fexample.com\u002Fvideo.mp4\"\n    src=\"https:\u002F\u002Fexample.com\u002Fimage.jpg\"\n    @press-start=\"onPressStart\"\n    @video-play=\"onVideoPlay\"\n  \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nfunction onPressStart() {\n  console.log('开始长按')\n}\n\u003C\u002Fscript>\n```\n\n## 技术难点解决\n\n### 1. 跨平台兼容性\n\n统一的视频控制接口处理不同小程序平台的差异：\n\n```typescript\nfunction getVideoContext() {\n  const videoId = `live-photo-video-${componentId.value}`\n  return uni.createVideoContext(videoId, instance)\n}\n```\n\n### 2. 状态同步\n\n确保视觉状态与实际播放状态的同步：\n\n```typescript\nfunction onVideoPlay() {\n  isVideoPlaying.value = true\n  emit('video-play')\n}\n\nfunction onVideoPause() {\n  isVideoPlaying.value = false\n  emit('video-pause')\n}\n```\n\n## 核心技术总结\n\n通过以上实现，我们成功在小程序中打造了一个功能完整、体验流畅的 LivePhoto 组件。该组件不仅解决了 `livephotoskitjs` 在小程序中无法使用的问题，还在性能和用户体验方面做了深度优化。\n\n### 关键技术要点\n\n1.  **双层叠加架构**：通过图片层和视频层的精确控制，实现无缝切换\n2.  **状态驱动设计**：使用响应式状态管理，确保 UI 与逻辑的一致性\n3.  **精确时序控制**：合理安排加载、播放、过渡的时机，提升用户体验\n4.  **性能优化策略**：懒加载 + 内存管理，避免资源浪费\n5.  **跨平台兼容性**：统一接口处理不同小程序平台的差异\n\n这个实现方案的核心在于**状态驱动的双层架构设计**，通过精确的状态管理和时序控制，在小程序环境下重现了 iOS LivePhoto 的交互体验。\n\n**该 LivePhoto 组件现已集成在 uni-ui-plus 组件库中**，开箱即用，无需从零开始实现。\n\n项目地址：[uni-ui-plus](https:\u002F\u002Fgithub.com\u002FIceyWu\u002Funi-ui-plus)\\\n在线文档：[LivePhoto 组件文档](https:\u002F\u002Funi-ui-plus-docs.netlify.app\u002Fcomponent\u002Flivephoto.html)\n\n演示小程序：[Life Palette](http:\u002F\u002Flpalette.cn\u002F) 体验版，暂未上线正式版本，可以在web端扫码申请体验\n\n如果你对这个小组件感兴趣，欢迎在 [GitHub](https:\u002F\u002Fgithub.com\u002FIceyWu\u002Funi-ui-plus) 上提交 issue 或 PR，一起完善它。\n",false,{"title":18,"description":18,"path":19,"id":20,"date":21,"tags":22,"lang":14,"rawbody":26,"draft":16},"从需求到实现：我的 SVG 动画玩具库开发小记","\u002Fposts\u002Fsvganimate","posts\u002Fposts\u002FSvgAnimate.md","2025-05-10 12:30:00",[23,24,25],"SVG","JavaScript","Web","---\ntitle: 从需求到实现：我的 SVG 动画玩具库开发小记\ndate: 2025-05-10 12:30:00\ndescription: 从需求到实现：我的 SVG 动画玩具库开发小记\ntags:\n  - SVG\n  - JavaScript\n  - Web\nlang: zh-cn\n---\n\n\n\n## 动机\n\n周末闲暇时，我在做个人网站时想到一个有趣的需求：想让 SVG 图标有个描边动画效果。市面上有很多成熟的动画库，但大多功能繁杂、体积庞大。其实我只需要一个轻量的 SVG 动画解决方案，于是决定自己动手写一个。\n\n这就是[svg-animate-web](https:\u002F\u002Fgithub.com\u002FIceyWu\u002Fsvg-animate-web)的由来 —— 一个完全为个人兴趣开发的小工具，纯粹是解决自己的需求，顺便分享给有类似需求的同学。\n\n## 核心理念\n\n作为一个周末玩具项目，我给自己设定了几个简单规则：\n\n- 只实现自己需要的功能，不追求大而全\n- 无第三方依赖，纯原生实现\n- 尽量保持 API 简洁明了\n\n## 实现原理剖析\n\n与其展示大量使用示例，我想和大家分享下这个小工具的核心实现原理和一些有趣的技术点。\n\n## 演示\n\u003Ciframe\n      class=\"code-editor-frame\"\n      data-code=\"code-editor-element\"\n      data-code-id=\"7502654189516881972\"\n      data-src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7502654189516881972\"\n      style=\"display: block;width: 100%; height: 400px;\"\n      loading=\"lazy\"\n      src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7502654189516881972\"\n>\u003C\u002Fiframe>\n\n## 效果\n\n![demo.gif](https:\u002F\u002Fp0-xtjj-private.juejin.cn\u002Ftos-cn-i-73owjymdk6\u002F35868340435d41c199cd1fe2c65b3540~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgSWNleVd1:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTI3MTQ1NTQzNjc4MTM1MiJ9&rk3s=e9ecf3d6&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1746936722&x-orig-sign=fpxAdylbxSTcjI8g0HZMrLZUphQ%3D)\n\n### 描边动画的实现\n\nSVG 描边动画的核心是利用`stroke-dasharray`和`stroke-dashoffset`这两个属性。源码中的核心实现如下：\n\n```javascript\n\u002F\u002F 路径动画应用函数核心逻辑\nfunction applyPathAnimation(pathElement, options) {\n  \u002F\u002F 计算路径长度\n  let pathLength = 0;\n  try {\n    pathLength = pathElement.getTotalLength(); \u002F\u002F 获取路径总长度\n  } catch (e) {\n    \u002F\u002F 降级处理\n    const bbox = pathElement.getBBox();\n    pathLength = 2 * (bbox.width + bbox.height);\n  }\n\n  \u002F\u002F 生成唯一ID\n  const id = Math.random().toString(36).substring(2, 10);\n\n  \u002F\u002F 设置初始样式\n  setStyle(pathElement, {\n    stroke: options.stroke,\n    strokeWidth: options.strokeWidth,\n    strokeDasharray: pathLength,\n    strokeDashoffset: pathLength,\n    animation: `animation${id} ${options.duration}s ${options.easing} ${options.delay}s ${options.count} forwards`,\n  });\n\n  \u002F\u002F 插入动画关键帧\n  insertKeyframes(`\n    @keyframes animation${id} {\n      0% { stroke-dashoffset: ${pathLength}; }\n      100% { stroke-dashoffset: 0; }\n    }\n  `);\n}\n```\n\n这里的技术要点有：\n\n1. 使用`getTotalLength()`获取路径长度，同时提供降级方案\n2. 为每个动画生成唯一 ID，避免 CSS 冲突\n3. 动态创建和插入 CSS 关键帧，而非使用 JavaScript 动画\n\n### 动态样式和关键帧注入\n\n为了避免污染全局 CSS 命名空间，我采用了动态生成和注入 CSS 的方式：\n\n```javascript\nfunction insertKeyframes(keyframes) {\n  let styleEl = document.getElementById(\"svg-animate-keyframes\");\n\n  if (!styleEl) {\n    styleEl = document.createElement(\"style\");\n    styleEl.id = \"svg-animate-keyframes\";\n    document.head.appendChild(styleEl);\n  }\n\n  try {\n    const sheet = styleEl.sheet;\n    if (sheet) {\n      sheet.insertRule(keyframes, sheet.cssRules.length);\n    } else {\n      throw new Error(\"Style sheet not available\");\n    }\n  } catch (e) {\n    \u002F\u002F 回退处理\n    styleEl.textContent += keyframes;\n  }\n}\n```\n\n这段代码有个有趣的地方：我先尝试使用 CSSOM API 直接操作样式表，如果失败则回退到直接修改`textContent`。这增加了兼容性，同时尽量使用更高效的 API。\n\n### 不同元素类型的处理\n\n使用了简单的条件判断区分不同 SVG 元素：\n\n```javascript\nexport function setPathAnimation(element, options) {\n  if (!element || !(element instanceof SVGElement)) return;\n\n  \u002F\u002F 检查是否为矩形元素\n  const isRect = element.tagName.toLowerCase() === \"rect\";\n\n  if (isRect) {\n    \u002F\u002F 应用矩形特有动画\n    applyRectAnimation(element, {\n      \u002F\u002F 配置参数\n    });\n  } else {\n    \u002F\u002F 应用路径元素动画\n    applyPathAnimation(element, {\n      \u002F\u002F 配置参数\n    });\n  }\n}\n```\n\n这种朴素的方式虽然不够优雅，但对于一个个人玩具项目来说足够简单明了，也便于理解和修改。\n\n## 一些有趣的实现细节\n\n### 自动处理 SVG 元素原始样式\n\n在应用动画时，需要考虑 SVG 元素可能已有的样式。比如如何优雅处理已有的 fill 和 stroke 属性：\n\n```javascript\nfunction getElementFillColor(element, defaultColor, userColor) {\n  if (userColor) return userColor;\n\n  const inlineFill = element.getAttribute(\"fill\");\n  if (inlineFill && inlineFill !== \"none\") return inlineFill;\n\n  try {\n    const computedFill = window.getComputedStyle(element).fill;\n    if (\n      computedFill &&\n      computedFill !== \"none\" &&\n      computedFill !== \"rgb(0, 0, 0)\"\n    ) {\n      return computedFill;\n    }\n  } catch (e) {\n    \u002F\u002F 忽略计算样式错误\n  }\n\n  return defaultColor;\n}\n```\n\n这个函数会按优先级依次检查：\n\n1. 用户指定的颜色\n2. 元素的内联 fill 属性\n3. 元素的计算样式\n4. 默认颜色\n\n这种细节处理让库在实际使用时更加健壮。\n\n### 性能考量\n\n即使是个人玩具项目，我也注重性能。比如在处理多个 SVG 元素时，采用了延迟执行的策略：\n\n```javascript\nexport function setSvgAnimation(svgElement, options) {\n  if (!svgElement) return;\n\n  const pathElements = svgElement.querySelectorAll(\n    \"path, line, polyline, polygon, rect, circle, ellipse\"\n  );\n\n  Array.from(pathElements).forEach((element, index) => {\n    if (!(element instanceof SVGElement)) return;\n\n    const elementOptions = { ...options };\n    elementOptions.delay = (options?.delay ?? 0) + index * 0.1; \u002F\u002F 错开动画开始时间\n    setPathAnimation(element, elementOptions);\n  });\n}\n```\n\n通过为每个元素设置递增的延迟，既创造了序列动画效果，又避免了同时执行大量动画带来的性能问题。\n\n\n## 未来可能的改进\n\n虽然作为一个个人玩具项目，但确实有一些有趣的改进点：\n\n- 添加更多元素类型的专用动画\n- 优化动画性能，特别是对于复杂 SVG\n- 增加更多动画控制选项\n\n如果你对这个小工具感兴趣，欢迎在 [GitHub](https:\u002F\u002Fgithub.com\u002FIceyWu\u002Fsvg-animate-web) 上提交 issue 或 PR，一起完善它。\n",{"title":28,"description":29,"path":30,"id":31,"date":32,"tags":33,"lang":14,"rawbody":34,"draft":16},"moveBefore() DOM API","Chrome 133 新特性：moveBefore() DOM API 让元素移动更优雅","\u002Fposts\u002Fmovebefore","posts\u002Fposts\u002FmoveBefore.md","2025-02-27 17:23:00",[24],"---\ntitle: moveBefore() DOM API\ndate: 2025-02-27 17:23:00\ndescription: Chrome 133 新特性：moveBefore() DOM API 让元素移动更优雅\ntags:\n  - JavaScript\nlang: zh-cn\n---\n\n## 前言\n\n2025 年 2 月 4 日，Chrome 133 正式发布，带来了一个令人期待的新功能：`moveBefore()` DOM API。这个新 API 为开发者提供了一种更优雅的方式在 DOM 中移动元素，避免了传统方法中状态丢失的烦恼。让我们一起来看看它是如何工作的，以及它能为你的项目带来什么改变！\n\n## DOM 移动的痛点\n\n你可能经常使用 `appendChild()` 或 `insertBefore()` 来操作 DOM。但你有没有注意到，当你尝试“移动”一个已有元素时，背后其实是先移除再插入的过程？这种机制源于 1998 年首个 DOM 标准的设计，至今未变。\n\n对于简单的元素（比如 `\u003Cp>`），这种“移除并插入”通常不会有什么问题。但如果涉及复杂节点，比如：\n\n- `\u003Ciframe>`（视频播放状态）\n- 全屏元素\n- CSS 动画\n- 用户输入字段（焦点状态）\n\n隐式的移除操作会导致状态重置，用户体验受损，开发者也只能无奈地编写复杂的解决办法，比如 MorphDOM 库，或者面对无解的 bug 报告。\n\n试试在 DOM 树中移动一个带有 CSS 动画或 `\u003Ciframe>` 的元素，你会发现动画重启、视频暂停，甚至焦点丢失——这些副作用让人头疼。\n\n## moveBefore()：真正的“移动”操作\n\n为了解决这一难题，Chrome 团队引入了全新的 `moveBefore()` API。它与 `insertBefore()` 的参数相同，但关键区别在于：它是一个**原子移动**操作。也就是说，目标节点会被直接移动到新位置，而不会经历“移除再插入”的过程，因此大多数状态都能保留。\n\n这个新 API 让开发者可以：\n\n- **保留视频播放状态**：无论是 `\u003Cvideo>` 还是 `\u003Ciframe>`，用户浏览时视频不会中断。\n- **保持焦点不丢**：移动输入字段时，用户的光标位置不变。\n- **动画无缝衔接**：添加或移除内容时，动画继续运行。\n- **提升变形算法效率**：更精确地协调现有 DOM 与新内容。\n- **支持动态 UI**：模态框、弹出窗口、全屏元素状态无损移动。\n\n## 代码示例\n\n下面是一个简单的示例，展示如何使用 `moveBefore()` 将一个带有动画的元素从一个容器移动到另一个容器，同时保留动画状态：\n\n\u003Ciframe\n      class=\"code-editor-frame\"\n      data-code=\"code-editor-element\"\n      data-code-id=\"7475978062035681289\"\n      data-src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7475978062035681289\"\n      style=\"display: block;width: 100%; height: 400px;\"\n      loading=\"lazy\"\n      src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7475978062035681289\"\n>\u003C\u002Fiframe>\n\n在这个示例中，当你点击“使用 moveBefore 移动”按钮时，元素会从容器 1 移动到容器 2，同时动画状态保持不变。而当你点击“使用 insertBefore 移动”按钮时，元素会先从容器 1 移除，再插入到容器 2，导致动画重启。\n\n## 如何体验？\n\n想试试这个新功能？有两种方式：\n\n1. **实验性尝试**：在 Chrome 中启用 `chrome:\u002F\u002Fflags\u002F#atomic-move` 标志，然后访问官方演示网站体验。\n2. **正式使用**：等到 2025 年 2 月 4 日 Chrome 133 发布，直接在项目中调用。\n\n目前，Chrome 团队还在推动将 `moveBefore()` 引入其他浏览器，填补 Web 平台的长久空白。\n\n## 为什么这很重要？\n\n对于 JavaScript 开发者来说，`moveBefore()` 不只是一个新工具，它解放了动态用户体验的设计空间。无论是构建流畅的动画、稳定的视频播放，还是复杂的交互组件，这个 API 都让一切变得更简单、更可靠。\n\n---\n[*参考：Chrome 官方公告（2025 年 2 月）*](https:\u002F\u002Fdeveloper.chrome.google.cn\u002Fblog\u002Fmovebefore-api)\n",{"title":36,"description":36,"path":37,"id":38,"date":39,"tags":40,"lang":14,"rawbody":41,"draft":16},"探秘图片 EXIF 数据：解锁隐藏信息","\u002Fposts\u002Fexif","posts\u002Fposts\u002FEXIF.md","2025-02-24 14:23:00",[24],"---\ntitle: 探秘图片 EXIF 数据：解锁隐藏信息 \ndate: 2025-02-24 14:23:00\ndescription: 探秘图片 EXIF 数据：解锁隐藏信息 \ntags:\n  - JavaScript\nlang: zh-cn\n---\n\n\n\n## 前言\n在数字化浪潮中，图片已然成为记录生活点滴的重要载体。然而，我们常常将目光聚焦于画面本身，却忽略了图片背后默默潜藏的 EXIF 数据。它宛如图片的“专属身份证”，蕴含着拍摄时的诸多关键信息。接下来，让我们一同深入探寻 EXIF 数据的奥秘。\n\n## 解析 EXIF 数据\n\nEXIF，全称为 Exchangeable Image File Format（可交换图像文件格式），是一种专门用于存储数码照片额外元数据的标准格式。这些元数据内容丰富多样，涵盖拍摄日期、相机型号、焦距、光圈、快门速度、ISO 感光度等拍摄参数，甚至还可能包含 GPS 地理位置信息。如今，无论是数码相机还是智能手机，在拍摄照片时大多会自动添加这些宝贵的 EXIF 数据。若你想了解更多详细的 EXIF 标签信息，可查阅 [EXIF 对照表](https:\u002F\u002Fexiv2.org\u002Ftags.html)。\n\n## EXIF 数据的多元应用\n\n### 助力摄影爱好者精进技艺\n\n对于摄影爱好者而言，EXIF 数据犹如一座宝藏。通过查看他人照片的 EXIF 信息，他们可以深入了解拍摄时所使用的参数，进而借鉴宝贵的拍摄技巧。例如，在夜景拍摄中，长曝光和低 ISO 设置常常能创造出令人惊艳的效果，这些经验都可以从 EXIF 数据中获取，为自己的拍摄实践提供有益参考。\n\n### 丰富旅行记录体验\n\nEXIF 数据还能为旅行记录增添别样的色彩。当照片中包含 GPS 信息时，我们可以将其与地图相结合，轻松创建地理标记照片集。这样一来，在回顾旅行经历时，每一张照片都能精准地定位到拍摄地点，让旅行足迹直观地呈现在眼前，仿佛重新踏上那段美好的旅程。\n\n## 查看 EXIF 数据的实用方法\n\n### 利用图片查看软件\n\n在日常使用中，我们可以借助常见的图片查看软件来查看 EXIF 数据。例如，Windows 系统中的“照片”应用和 macOS 系统中的“预览”应用，只需在相应的选项卡中即可轻松找到这些信息。\n\n### 通过编程实现查看\n\n对于开发者来说，使用 JavaScript 和 [exif-js](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fexif-js) 库可以方便地读取图片的 EXIF 数据。以下是一段示例代码：\n\n```html\n\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Ctitle>Exif.js Example\u003C\u002Ftitle>\n    \u003Cscript src=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fexif-js\">\u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cimg src=\"\u002Ftest_exif.jpg\" alt=\"测试 EXIF 数据的图片\" style=\"object-fit: contain; height: 300px\" id=\"img1\" crossorigin=\"anonymous\" \u002F>\n    \u003Cpre id=\"exifData\">\u003C\u002Fpre>\n    \u003Cscript>\n      window.onload = function () {\n        var img1 = document.getElementById(\"img1\");\n        img1.onerror = function () {\n          document.getElementById(\"exifData\").textContent = \"图片加载失败，请检查图片路径。\";\n        };\n\n        EXIF.getData(img1, function () {\n          try {\n            var allMetaData = EXIF.getAllTags(this);\n            console.log(\"🐠-----allMetaData-----\", allMetaData);\n            var output = \"\";\n            for (var tag in allMetaData) {\n              output += `${tag}: ${allMetaData[tag]}\\n`;\n            }\n            document.getElementById(\"exifData\").textContent = output;\n          } catch (error) {\n            document.getElementById(\"exifData\").textContent = \"无法读取 EXIF 数据：\" + error.message;\n          }\n        });\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n数据展示\n\n![info\\_demo.png](https:\u002F\u002Fp0-xtjj-private.juejin.cn\u002Ftos-cn-i-73owjymdk6\u002Fa0c8617b080242fbaa1808f88f0bbf77~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgSWNleVd1:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTI3MTQ1NTQzNjc4MTM1MiJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1741595603&x-orig-sign=yZhvrnRxUwmgME49B%2FsSjeeC9oU%3D)\n\n### 如果采用了OSS存储图片，如何查看EXIF数据？\n\n如果图片存储在OSS中，你可以使用`url + '?x-oss-process=image\u002Finfo'`的方式来获取图片的EXIF数据，[博客链接](https:\u002F\u002Fhelp.aliyun.com\u002Fzh\u002Foss\u002Fuser-guide\u002Fquery-the-exif-data-of-an-image-4?spm=5176.21213303.J_v8LsmxMG6alneH-O7TCPa.1.707f2f3dIGZ3HC\\&scm=20140722.S_help@@%E6%96%87%E6%A1%A3@@44975._.ID_help@@%E6%96%87%E6%A1%A3@@44975-RL_exif-LOC_2024SPAllResult-OR_ser-PAR1_2150434f17403812847011792efa20-V_4-RE_new6-P0_0-P1_0)。\n\n例如：\u003Chttps:\u002F\u002Fnest-js.oss-accelerate.aliyuncs.com\u002FnestTest\u002F1\u002F1738636470704.JPG?x-oss-process=image\u002Finfo>\n\n## 应用\n\n获取到EXIF数据后，你可以根据自己的需求进行应用。例如，将原来只有图片的相册，加上图片的拍摄时间，拍摄地点等信息，就可以形成一个更加丰富的相册。\n\n![use\\_demo\\_2.png](https:\u002F\u002Fp0-xtjj-private.juejin.cn\u002Ftos-cn-i-73owjymdk6\u002Ff740324a5d4146f4a08001baf22eb097~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAgSWNleVd1:q75.awebp?policy=eyJ2bSI6MywidWlkIjoiMTI3MTQ1NTQzNjc4MTM1MiJ9&rk3s=f64ab15b&x-orig-authkey=f32326d3454f2ac7e96d3d06cdbb035152127018&x-orig-expires=1741595603&x-orig-sign=Ra1K4Mpw3Lm89QtBQTAUPH71MX0%3D)\n\n## 总结\n\n借助EXIF数据可以增添我们图片的色彩，让片刻回忆更具体化，我把它运用到了开源项目[LifePalette-Web](https:\u002F\u002Fgithub.com\u002FLife-Palette\u002FLifePalette-Web)的图片预览中，如果你也对这个功能感兴趣，可以访问下面预览地址以及工程了解一下，也期待您的参与和star。\n\n预览地址：[LifePalette-Web](https:\u002F\u002Flpalette.cn)\n\n项目地址：[LifePalette-Web](https:\u002F\u002Fgithub.com\u002FLife-Palette\u002FLifePalette-Web)\n",{"title":43,"description":43,"path":44,"id":45,"date":46,"tags":47,"lang":14,"rawbody":48,"draft":16},"Vue LivePhoto(实况图片)渲染","\u002Fposts\u002Flivephoto","posts\u002Fposts\u002FLivePhoto.md","2024-11-06 17:23:00",[10],"---\ntitle: Vue LivePhoto(实况图片)渲染 \ndate: 2024-11-06 17:23:00\ndescription: Vue LivePhoto(实况图片)渲染 \ntags:\n  - Vue\nlang: zh-cn\n---\n\n\n\n## 前言\n\n最近使用 iPhone 拍摄了一些实况照片，发现了一个很有意思的功能，`LivePhoto`，它可以将照片变成一个小视频，如果把这个特性应用到[LifePalette-Web](https:\u002F\u002Fgithub.com\u002FLife-Palette\u002FLifePalette-Web)项目中，会不会很有趣呢？\n\n## 探索 `LivePhoto`\n\n网上对于 vue 实现`LivePhoto`的文章很少，搜索相关资料发现苹果官方提供了一个`LivePhoto`的`js`库，[LivePhotoKit](https:\u002F\u002Fdeveloper.apple.com\u002Fdocumentation\u002Flivephotoskitjs)，它可以帮助我们实现`LivePhoto`的渲染，于是我就尝试使用这个库来实现`LivePhoto`的渲染\n\n\u003Ciframe\n      class=\"code-editor-frame\"\n      data-code=\"code-editor-element\"\n      data-code-id=\"7477495222440558632\"\n      data-src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7477495222440558632\"\n      style=\"display: block;width: 100%; height: 400px;\"\n      loading=\"lazy\"\n      src=\"https:\u002F\u002Fcode.juejin.cn\u002Fpen\u002F7477495222440558632\"\n>\u003C\u002Fiframe>\n\n\n由官网 demo 发现实况图片其实是由两部分组成的，一部分是图片，一部分是视频，图片和视频是一一对应的，这里我们可以通过`data-photo-src`和`data-video-src`来指定图片和视频的地址，这样就可以实现实况图片的渲染\n\n## 在Vue中使用`LivePhoto`\n\n官方提供了 livephotoskit.js 库，我们可以 npm 安装这个库\n\n```bash\n\npnpm install livephotoskit\n\n```\n\n实现\n\n```vue\n\n\u003Cscript setup>\nimport * as LivePhotosKit from 'livephotoskit'\n\nconst livePhotoRef = ref()\nconst fileInfo = {\n    src: 'https:\u002F\u002Fnest-js.oss-accelerate.aliyuncs.com\u002FnestTest\u002F1\u002F1711965193523.JPEG',\n    videoSrc: 'https:\u002F\u002Fnest-js.oss-accelerate.aliyuncs.com\u002FnestTest\u002F1\u002F1711965199299.MOV',\n}\n\nasync function initLivePhoto() {\n    await nextTick()\n    const player = LivePhotosKit.Player(livePhotoRef.value)\n    player.photoSrc = fileInfo.src\n    player.videoSrc = fileInfo.videoSrc\n}\n\nonMounted(() => {\n    initLivePhoto()\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cdiv ref=\"livePhotoRef\" class=\"w-100 h-100\" \u002F>\n\u003C\u002Ftemplate>\n\u003Cstyle lang=\"less\" scoped>\u003C\u002Fstyle>\n\n```\n\n## 总结\n\n通过这次探索，我们成功的实现了`LivePhoto`的渲染，这个功能可以让我们的项目更加有趣，如果你也对这个功能感兴趣，可以尝试一下\n\n项目地址：[LifePalette-Web](https:\u002F\u002Fgithub.com\u002FLife-Palette\u002FLifePalette-Web)\n\n预览地址：[LifePalette-Web](https:\u002F\u002Flpalette.cn)\n",{"title":50,"description":51,"path":52,"id":53,"date":54,"tags":55,"lang":14,"rawbody":57,"draft":16},"关于 Tauri","探索 Tauri","\u002Fposts\u002Ftauri","posts\u002Fposts\u002FTauri.md","2024-05-16 17:23:00",[56],"Rust","---\ntitle: 关于 Tauri\ndate: 2024-05-16 17:23:00\ndescription: 探索 Tauri\ntags:\n  - Rust\nlang: zh-cn\n---\n\n\n\n\n\n## Tauri\n\n[Rust](https:\u002F\u002Fwww.rust-lang.org\u002Fzh-CN\u002Ftools\u002Finstall)下载快捷安装，`rustc --version`查看是否安装成功，最好重启\n\n### Rust Window 下配置国内源\n\n用户主目录 中有一个`.cargo` 目录。其中用户主目录一般是 `C:\\Users\\ +当前用户名 为路径的目录`。`.cargo`目录,新建 `config`(`config.toml`)文件，编辑内容：\n\n```\n[source.crates-io]\nreplace-with='rsproxy'\n[source.rsproxy]\nregistry=\"https:\u002F\u002Frsproxy.cn\u002Fcrates.io-index\"\n[registries.rsproxy]\nindex = \"https:\u002F\u002Frsproxy.cn\u002Fcrates.io-index\"\n[net]\ngit-fetch-with-cli = true\n```\n\n### 打包加速-WixTools\n\n加速网站[git下载加速](https:\u002F\u002Fmoeyy.cn\u002Fgh-proxy)，下载地址[WixTools-https:\u002F\u002Fgithub.com\u002Fwixtoolset\u002Fwix3\u002Freleases\u002Fdownload\u002Fwix3112rtm\u002Fwix311-binaries.zip](https:\u002F\u002Fgithub.com\u002Fwixtoolset\u002Fwix3\u002Freleases\u002Fdownload\u002Fwix3112rtm\u002Fwix311-binaries.zip)\n",1777187551674]