昨天留下的待解問題是:Figma Motion 匯出的 dotLottie,播放時會不會自己尊重使用者的「減少動態」偏好。今天直接讀原始碼回答:dotlottie-web 官方倉庫在 2026 年 9 月 15 日那個提交(a08deda,@lottiefiles/dotlottie-web 0.80.0)裡,整個倉庫搜不到 reduced-motion、reducedMotion,也搜不到 aria,內建行為是沒有,責任完全在接手播放的前端。昨天我推測「Figma 的 easing 變數多個 mode 可以拿來做減少動態版」,今天也查了一圈:這個點子確實有人做出來,但是做在第三方外掛上,不是 Figma 原生功能,而且 mode 不會跟著匯出檔走。以下只談介面與流程,財經消息不碰。

📖 學

一、dotlottie-web 到底有沒有處理減少動態

我的做法是把官方倉庫(LottieFiles/dotlottie-web)淺層複製下來,在 a08deda 這個提交上全文搜尋。結果有三項:

搜尋目標結果
reduced-motion、reducedMotion、prefers-reduced整個倉庫零筆
aria、role=(packages/web、react、wc、vue 的 src)零筆,播放器不會替 canvas 加任何無障礙屬性
matchMedia只出現在示範用的 apps/viewer,查的是 prefers-color-scheme 與 min-width: 768px,跟動態偏好無關

另外兩個行為要分清楚,因為它們容易被誤讀成「有處理」。第一,建構時 autoplay 與 loop 的預設都是 false(原始碼是 config.autoplay ?? false、config.loop ?? false),但這只是核心層的預設,包裝元件與你的範例程式一旦寫了 autoplay,它就會自動播。第二,renderConfig.freezeOnOffscreen 預設是 true,離開視窗會停止渲染,原始碼註解寫的理由是避免不必要的繪製,這是效能機制,跟使用者偏好無關。

這個結論有兩層邊界。我只看了預設分支當下的程式碼,沒有逐版本回溯;GitHub 的 issue 搜尋用 reduced-motion、prefers-reduced-motion 查 dotlottie-web 都是零筆,但搜尋索引不保證涵蓋全部,所以「沒有人提過」我不下斷言,只說「我查不到」。而這個設計不是 dotLottie 才有:airbnb/lottie-web 在 2020 年 2 月 13 日就有人開過功能請求(1986 號 issue,2020 年 3 月 6 日以完成狀態關閉),維護者當時的回覆是「這應該由開發者處理而不是函式庫,每個情境需要的解法不同,可以換成靜態圖、停在第一格或最後一格」。也就是說,整個 Lottie 生態一直是把減少動態當成「應用端政策」,不是播放器功能。

二、最小可行的應用端寫法

政策先於程式碼:減少動態不等於「全部不要動」。web.dev 的指南明確警告,動畫常承擔回饋與狀態傳達,建議的做法是「減少裝飾性動態,保留功能性動態」。所以先把你的 Lottie 分兩類,再決定停在哪一格。

動畫類型偏好為 reduce 時理由
裝飾(首頁 hero 插圖、背景循環)停在代表性的一格,不循環無資訊量,純氛圍
狀態回饋(成功勾勾、上傳完成)播一次不循環,或直接顯示最終格要傳達結果,但不需要來回彈跳
載入指示保留,但降低幅度或改用不動的進度文字屬「必要」動態,見 2.2.2 的例外

程式碼面,我用原始碼核對過下列成員都存在:建構選項 canvas、src、autoplay、loop,方法 pause()、setFrame()、setLoop(),屬性 totalFrames,事件 load。

const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
const player = new DotLottie({
  canvas,
  src: '/anim/success.lottie',
  autoplay: !mq.matches,
  loop: !mq.matches,
});
 
player.addEventListener('load', () => {
  if (mq.matches) player.setFrame(player.totalFrames - 1); // 停在最終格
});
 
mq.addEventListener('change', (e) => {
  if (e.matches) { player.setLoop(false); player.pause(); }
});

這段我沒有在瀏覽器實跑,只核對了名稱與簽章;setFrame 在檔案載入完成前會直接略過(原始碼在核心物件為空時 return),所以才放在 load 事件裡。totalFrames - 1 是否剛好對應「視覺上的最終格」要看你的動畫檔,實測時再確認。偏好從 reduce 切回無偏好時,上面這段不會自動重新播放,這是刻意的保守,要不要恢復看產品決定。

三、5 秒規則的計算

昨天提過的 WCAG 2.2.2 閾值,在 Lottie 上可以直接算。動畫時長 = 總格數 ÷ 影格率。例:90 格、30 fps,時長 3 秒;若 loop: true,它就不會自己結束,等於自動開始且持續超過 5 秒,並列呈現時就落入 2.2.2,需要暫停控制或在 5 秒內停止。同一個檔案改成不循環,3 秒播完就低於門檻。反過來,一個 6 秒、不循環的 hero 動畫自動播放,也已經超過 5 秒。所以「是否循環」與「總時長」兩個數字,應該在動效交付表裡就寫明,而不是等工程端才發現。

四、Figma 這端:mode 做得到什麼、做不到什麼

昨天我說 easing 變數支援多個 mode,今天更正並補一項:Figma 說明中心〈Overview of variables, collections, and modes〉列出的變數型別共六種,除了顏色、數字、字串、布林,還有專門的 Timing(以毫秒表示的時間)與 Easing(預設或自訂的曲線或彈簧)。所以時長與曲線都能進變數,也都能放進多個 mode,理論上可以建一個「標準」與一個「減少動態」mode:Timing 縮短、Easing 換成 Hold(直接跳到終點)。Plugin API 的型別裡也確實有 HOLD。

但三件事要分清楚。第一,原生文件沒有任何一處提到減少動態;我另搜到的 $reduced-motion ? "Motion/Hold" : "Motion/Bouncy" 這種條件運算式,出自第三方外掛 Dynamic Variables(作者 Neel TS)的文件,不是 Figma 原生功能,Figma 官方 Easing 說明頁我也讀過,確認沒有這個主題。第二,Figma 的 mode 是在設計稿裡切頁面或框架的情境,我查不到它能綁定到使用者作業系統偏好的機制,這一項屬推論:mode 是設計時的情境切換,不是執行時的使用者偏好。第三,匯出檔不一定帶著這些資訊:Figma 〈Export animations from Figma〉頁面對於變數或 mode 是否進入匯出的 Lottie 完全沒有說明;LottieFiles 的文件寫明「Motion tokens 只在 dotLottie 存在,匯出成 Lottie JSON 會移除它們以及狀態機與主題」。要注意,那是 LottieFiles 自家 Figma 外掛的「Motion tokens」概念,與 Figma 原生 Motion 的變數是兩套東西,不能互相替代。

所以務實的結論是:把「減少動態」版當成另一個獨立匯出檔最穩(同一份設計、Hold 曲線、極短時長,匯出第二份),由前端依偏好選用;Figma 的 mode 拿來在設計階段並排檢視兩個版本是合理的,但不能假設它會自動變成執行時的開關。另外,LottieFiles 針對 Figma Motion 匯出的說明頁(最後更新 2026 年 9 月 11 日)列出「動畫文字內容」不支援,而 Figma 9 月 30 日才新增逐字動畫;兩者時間上有落差,我沒有實測,所以逐字動畫能否完整匯出成 Lottie 仍是待查,匯出後務必在你的正式播放器上實看。

🧠 記

  • dotlottie-web 不會替你處理減少動態。 我查證的事實:a08deda(2026-09-15)全倉庫零筆 reduced-motion 與 aria;autoplay 與 loop 核心預設為 false,但元件層一寫就會自動播。邊界:只看了預設分支一個時間點,issue 搜尋零筆不代表從沒人提。
  • 減少動態是政策,不是開關。 先分裝飾、狀態回饋、必要三類再決定停格、播一次或保留;web.dev 的原則是減少裝飾性動態、保留功能性動態。
  • Figma 的 mode 不等於執行時偏好。 Timing 與 Easing 都是變數型別,可放進多個 mode,但原生文件沒有減少動態、也沒有說明 mode 是否進匯出檔;$reduced-motion 運算式是第三方外掛的功能。最穩的做法是另匯出一份減少動態版。

✍️ 實踐

挑一個你專案裡已經在用的 Lottie(或準備匯出的 Figma Motion 動畫),30 分鐘內做完:

  1. 寫動效交付表。 兩欄必填:總時長(總格數 ÷ 影格率)與是否循環。套 5 秒規則,標出會落入 2.2.2 的項目。
  2. 分類。 把每個動畫標成裝飾、狀態回饋、必要三類,並決定 reduce 時的處理(停格、播一次、保留)。
  3. 接上偏好。 照上面的程式碼模式加上 matchMedia 判斷;若不是用 dotlottie-web,先到你的播放器原始碼或文件確認它有沒有內建,沒有就自己接。
  4. 實測。 開系統的減少動態(macOS:系統設定 → 輔助使用 → 顯示 → 減少動態),重新載入頁面,確認動畫沒有循環、停格位置合理。
  5. 明天要接的待辦。 在 Figma Motion 實際建一個逐字動畫,匯出 dotLottie,在 dotlottie-web 播放,確認逐字動畫是否完整保留,以及 Hold 曲線匯出後是否真的變成階梯式跳格;把結果對照 LottieFiles 說明頁「動畫文字內容不支援」那一行,做成更正或確認。

🔗 延伸學習

💬 問 AI

我在做 Figma Motion 匯出 dotLottie 並用 dotlottie-web 播放的專案,要處理使用者的「減少動態」偏好。請嚴格分成三區回答:「文章明說的」「你查證到的」「你的推論」,三區不能混;不確定就直接說不確定,不要編造連結或版本號。
 
文章明說的(請當成待查證的主張):
1. dotlottie-web 在 2026-09-15 的提交 a08deda(0.80.0)全倉庫沒有 reduced-motion 與 aria 的程式碼,autoplay 與 loop 的核心預設為 false,freezeOnOffscreen 預設為 true 但只與可視範圍有關。
2. airbnb/lottie-web 第 1986 號 issue 中,維護者認為減少動態應由開發者處理。
3. Figma 變數型別有 Timing 與 Easing,可放進多個 mode;原生文件未提到減少動態;$reduced-motion 條件運算式出自第三方外掛 Dynamic Variables。
4. Figma 的匯出說明頁沒有說明變數或 mode 是否進入 Lottie;LottieFiles 說 Motion tokens 只在 dotLottie 保留,JSON 會移除。
5. LottieFiles 的 Figma Motion 匯出說明頁(2026-09-11 更新)列「動畫文字內容」為不支援,Figma 於 2026-09-30 新增逐字動畫。
 
請做:
A. 逐點用官方文件與原始碼驗證 1–5,標示「查證成立/部分成立/查不到」並附連結。
B. 檢查 dotlottie-web 最新版本是否在 a08deda 之後新增了減少動態相關行為,若有,指出版本與提交。
C. 實測或從文件確認:Figma Motion 匯出的 Hold 曲線在 Lottie 裡是否為階梯式關鍵影格,逐字文字動畫匯出後是否保留。
D. 在「推論」區回答:要用單一檔案加執行時開關,還是匯出兩份檔案(標準與減少動態)?各自的維護成本與無障礙風險是什麼?
E. 給我一份 8 項以內的上線前檢查表,涵蓋 WCAG 2.2.2 的 5 秒規則、循環設定、reduce 時的停格位置與必要動態的例外。