Remotion LabRemotion Lab
疑難排解次像素渲染問題

次像素渲染問題

說明 Chrome 次像素渲染如何影響 Remotion 動畫的平滑度,以及如何使用 transform 和 perspective() 修復文字動畫的抖動問題

次像素渲染問題

什麼是次像素渲染

Chrome 預設會將文字對齊到最接近的像素。這意味著 margin-top: 10pxmargin-top: 10.4px 渲染出的效果完全相同。

這種行為會導致振盪效果,使您的動畫看起來不夠平滑,甚至出現抖動感。

問題範例

考慮以下場景:您想要文字從位置 0 平滑地移動到位置 50px,整個動畫持續 200 幀:

// ❌ 這種寫法可能出現抖動
import { interpolate, useCurrentFrame } from "remotion";
 
export const JitteringText = () => {
  const frame = useCurrentFrame();
 
  return (
    <div
      style={{
        marginTop: interpolate(frame, [0, 200], [0, 50]),
        // Chrome 會將小數像素值四捨五入到最近的整數
        // 導致動畫不連貫,出現跳動感
      }}
    >
      hi there
    </div>
  );
};

在此範例中,當 interpolate 產生如 10.4px10.6px 這樣的值時,它們都會被渲染為 10px11px,導致動畫在某些幀之間「跳格」。

解決方案:使用 transform 屬性

使用 transform 屬性移動元素,Chrome 對 transform 使用次像素精度渲染:

// ✅ 使用 transform,實現平滑動畫
import { interpolate, useCurrentFrame } from "remotion";
 
export const SmoothText = () => {
  const frame = useCurrentFrame();
 
  return (
    <div
      style={{
        transform: `translateY(${interpolate(frame, [0, 200], [0, 50])}px)`,
        // transform 使用次像素精度,動畫更平滑
      }}
    >
      hi there
    </div>
  );
};

進一步最佳化:加入 perspective() 和 willChange

transform 中加入 perspective() 並設定 willChange: "transform" 可以讓次像素渲染更加精確:

// ✅✅ 最佳寫法:加入 perspective 和 willChange
import { interpolate, useCurrentFrame } from "remotion";
 
export const OptimizedText = () => {
  const frame = useCurrentFrame();
 
  return (
    <div
      style={{
        transform: `perspective(100px) translateY(${interpolate(
          frame,
          [0, 200],
          [0, 50]
        )}px)`,
        willChange: "transform",
        // perspective() 觸發 GPU 加速渲染
        // willChange 提示瀏覽器提前進行最佳化
      }}
    >
      hi there
    </div>
  );
};

200x100 視訊的視覺差異

官方文件展示了一個 200x100 像素的視訊對比:

左側(有抖動):

<div
  style={{
    transform: `translateY(${interpolate(frame, [0, 200], [0, 50])}px)`,
  }}
>
  hi there
</div>

右側(平滑):

<div
  style={{
    transform: `perspective(100px) translateY(${interpolate(
      frame,
      [0, 200],
      [0, 50]
    )}px)`,
    willChange: "transform",
  }}
>
  hi there
</div>

使用 perspective()willChange: "transform" 的版本動畫明顯更加平滑。

適用場景和注意事項

適合使用此最佳化的情況

  • 包含緩慢移動文字的動畫
  • 精細的位置動畫(移動距離很小)
  • 高品質輸出要求

需要注意的地方

過多使用 willChange: "transform" 會消耗更多系統資源,因為它強制瀏覽器為每個元素建立獨立的合成層(compositing layer)。

建議的做法:

// 僅在渲染時啟用此最佳化
const IS_RENDERING = process.env.NODE_ENV === "production";
 
<div
  style={{
    transform: `perspective(100px) translateY(${y}px)`,
    willChange: IS_RENDERING ? "transform" : "auto",
  }}
>
  hi there
</div>

其他受次像素渲染影響的屬性

除了位置(topleftmargin)之外,以下屬性也受到次像素問題的影響:

屬性是否受影響建議替代方案
top / lefttransform: translateX/Y
margintransform: translateX/Y
width / heighttransform: scale()
font-sizetransform: scale()
transform: translateX/Y否(已使用次像素精度)

完整動畫範例

import { interpolate, useCurrentFrame } from "remotion";
 
export const SubpixelDemo = () => {
  const frame = useCurrentFrame();
 
  const yOffset = interpolate(frame, [0, 120], [0, 100], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });
 
  return (
    <div style={{ position: "relative", width: 400, height: 300 }}>
      {/* 啟用次像素最佳化的文字 */}
      <p
        style={{
          transform: `perspective(100px) translateY(${yOffset}px)`,
          willChange: "transform",
          fontSize: 24,
          color: "white",
        }}
      >
        平滑移動的文字
      </p>
    </div>
  );
};

相關資源