timerring

Add Live Photo Effect to Hugo

May 16, 2025 · 12 min read · Page View:
Weekly
Hugo

Phil Schiller 在iPhone 6s发布会上展示 Live Photo

If you have any questions, feel free to comment below.

Live Photos 作为最早 Apple 在2015年9月发布的 iPhone6s6s Plus 中引入的特色功能,很快就在手机摄影领域独树一帜,收获了无数忠实用户,Live Photo 的特色在于,它不仅仅是一张静态的照片,用户在拍摄实况照片时,iPhone会录下拍照前后各 1.5 秒所发生的一切。在 24 年下半年,随着微信宣布支持实况照片,Live Photo 再次成为了各大应用以及手机厂商的焦点,在短时间内无法实现更好的 AI 叙事的当下,不少手机厂商将 Live photo 适配各大社交媒体视为这个季度的业绩救赎,最近我也在思考,是否可以在博客中添加这个 feature,以便可以更好地展示照片的内容,在经过一番仔细搜索之后,发现网上完全没有关于 Hugo 支持 Live photo 的任何讨论,因此我决定自己动手实现这个 feature,也就有了本篇文章。

本文所有代码已经放置在 github 仓库 hugo-live-photo,欢迎提出自己的修改建议或者想法,会根据大家的想法实时更新修改。

Live photo 的历史 #

回顾 Live photo 的历史,尽管现在很多人会完全将 Live photo 和 iPhone 绑定在一起,但是实际上,在苹果推出 Live Photo 之前,手机厂商就开始探索介于视频与图片之间的记录方式。2012 年,诺基亚 Lumia 系列手机推出 Cinemagraph 应用,能让照片部分元素变成动画。2013 年,HTC 推出 Zoe 动态拍照,拍摄时可同时获得一张相片和一段 3 秒视频,这些为 Live Photo 的诞生奠定了一定基础1

HTC One2 HTC One2

随后2015 年,苹果公司在 iPhone 6s 发布会上推出 Live Photo3。它基于苹果的 3D Touch 技术,通过在显示屏下集成压力传感器,实现垂直方向的压力感应。用户使用 3D touch 功能按压屏幕,就可以查看 “动态照片”。Live Photo 在拍照时会收录拍照瞬间前后各 1.5 秒的画面和声音,将其与静态照片结合,让照片 “动” 起来4

Phil Schiller 在iPhone 6s发布会上展示 Live Photo3 Phil Schiller 在iPhone 6s发布会上展示 Live Photo3

尽管 IOS 靠这个特性赚的盆满钵满,Android 阵营的探索还是显得延迟不少,尽管去年华星OV等手机厂商都已经在旗舰款机型上支持了 Live Photo,但是实际适配社交平台的也仅仅只有 OPPO Reno12。

到底问题出在哪里?这还要从 Live photo 的原理说起。

Live photo 原理 #

IOS #

IOS 的 Live photo 原理相对简单5,你可以通过 iCloud 下载一张 Live photo,你会得到一张 .HEIC(High Efficiency Image Container) 后缀的图片以及一段 .MOV 后缀的视频,视频的时长为 3 秒。通过 ffprobe 我们可以查到 视频的编码是 HEVC(High Efficiency Video Coding) 6,即我们所熟知的 H.265 编码。当然 HEVC 是 HEIF(High Efficiency Image File Format)7 的技术实现, 虽然实况照片不是一张像GIF和WebP动图的照片,但在显示效果上和动图基本差不多。

简单总结一下三者之间的关系

名称类型核心功能与 HEVC 的关系
HEVC视频编码标准压缩视频,降低码率,支持超高清分辨率自身为独立标准,是 HEIF 的技术基础
HEIF图像文件格式存储单张或多张图像,支持动态内容和透明通道,基于 HEVC 帧内编码技术复用 HEVC 的帧内压缩算法
HEIC图像文件格式(HEIF 子集)存储单张静态图像,是 HEIF 的简化版,苹果设备默认使用属于 HEIF 的单图场景实现

Android #

相比于 IOS 的简易实现,Android 的实现就相对较为复杂,谷歌专门给 “ 动态照片 ” 设计了一套名叫 MicroVideo 的 “ 单文件 ” 标准( 后改名为 Motion Photo ),简单来说,就是将视频、音频、以及各类表明文件信息的数据,都封装进单个照片文件里了。而单个文件就相当于多种资料的压缩包,在 2024 年的七月份,谷歌也公开了 Motion Photo format 1.0 标准8。对于其他 Android 厂商来说,统一适配动态图片,还有很长的路要走9

动手实现 #

有了以上的分析,我们大概就有了实现 Live Photo 的思路,本质上 Live Photo 可以简易地理解为 Photo + Short Video 的结合,因此,要想在 Hugo 中实现 Live Photo 的显示,我们就需要自己实现一个 shortcode 模板,实现一个 script,访问时只是将 Live Photo 当做静态图片显示,当鼠标 hover 在图片上时,用自动播放的视频代替该图片。

shortcodes #

首先,在 shortcodes 下实现基本的模版 live-photo.html,实现资源的加载,如下所示:

{{- $image := .Get "image" | default (.Get 0) -}}
{{- $video := .Get "video" | default (.Get 1) -}}
{{- $desc := .Get "desc" | default (.Get 2) -}}
<div class="live-photo-container">
  <!-- image element -->
  <img src="{{- $image -}}" alt="Live Photo" class="live-photo-img" loading="lazy">
  <!-- video element -->
  <video class="live-photo-video" playsinline muted loop loading="lazy">
    <source src="{{- $video -}}" type="video/mp4">
    <p>
      Your browser doesn't support HTML5 video. Here is a
      <a href="{{- $video -}}">link to the video</a> instead.
    </p>
  </video>
  <!-- if description exists, then display -->
  {{- if $desc -}}
  <div class="live-photo-desc">{{- $desc -}}</div>
  {{- end -}}
</div>

注意,这里我给 video 添加的属性有 playsinline muted loop loading="lazy",可以根据自己实际需要更改,例如你想要在 hover 时播放声音,那么可以考虑将 muted 属性去除即可。

javascript 脚本 #

接着在 assets 下实现 live-photo.js,实现鼠标 hover 时的自动播放的逻辑,如下所示:

document.addEventListener('DOMContentLoaded', function() {
    const containers = document.querySelectorAll('.live-photo-container');
    
    containers.forEach(container => {
      const video = container.querySelector('.live-photo-video');
      
      video.load();
      
      // when the cursor hover
      container.addEventListener('mouseenter', () => {
        // auto play from the beginning
        video.currentTime = 0;
        const playPromise = video.play();
        
        if (playPromise !== undefined) {
          playPromise.catch(error => {
            console.log("Auto-play was prevented");
          });
        }
      });

      // when the cursor leave
      container.addEventListener('mouseleave', () => {
        // pause the video
        video.pause();
        // reset the video to the beginning
        video.currentTime = 0;
      });
    });
  });

引入脚本 #

为了使脚本生效,我们可以将脚本在每次构建时引入,在 layouts/partials/html-head.html 中添加如下代码:

{{- $livePhotoJS := resources.Get "live-photo.js"| resources.Minify | resources.Fingerprint }}
<script defer src="{{ $livePhotoJS.RelPermalink }}" {{ template "integrity" $livePhotoJS }}></script>

这使得每次在构建头文件时,都会确保引入 script 脚本。

定义样式 #

当然,可以在 assets 下的 _shortcodes.scss 中定义组件相关的样式,利用透明度实现照片与视频的无缝切换,例如:

// Find it under the yourtheme/assets/_shortcodes.scss file
// You can define your style code
.live-photo-container {
    position: relative;
    width: 60%;
    margin: 0 auto; 
    overflow: hidden;
    
    // image element
    .live-photo-img {
      width: 100%;
      height: auto;
      display: block;
      transition: opacity 0.2s ease-in-out;
    }
    
    // video element
    .live-photo-video {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: auto;
      object-fit: cover;
      opacity: 0;
      transition: opacity 0.2s ease-in-out;
    }
    
    // description element
    .live-photo-desc{
      font-size:small;
      color: gray;
      font-style: italic;
      margin-top: 0.5rem;
      text-align: center;
    }
    
    // hover change the opacity to implement the live photo
    &:hover {
      .live-photo-video {
        opacity: 1;
      }
      .live-photo-img {
        opacity: 0;
      }
    }
  }

当然,你可以自定义更多适合你的图片样式,这里不做赘述。

效果展示 #

通常绝大多数图床不支持显示 HEIC 的后缀,因此推荐用 magick 等工具将图片转为 webp,jpg 等格式方便访问。

同样多数图床超过 20 MB 的内容 cdn 无法加速,因此也推荐使用 ffmpeg 等工具,将 mov 视频降低码率使用 h.264 编码为 mp4 等 html 下支持的视频格式。

最终,我们可以使用 shortcodes 来引入 shortcodes,模板如下:

你可以尝试将鼠标放置在下面的图片上查看效果:

Live Photo
Play the live photo by simply hovering the mouse over it.

本文所有代码已经放置在 github 仓库 hugo-live-photo,欢迎提出自己的修改建议或者想法,会根据大家的想法实时更新修改。

References #

Related readings


<< prev | The Practice of... Continue strolling How to Choose... | next >>

If you find this blog useful and want to support my blog, need my skill for something, or have a coffee chat with me, feel free to: