2524 字
13 分鐘
Apple 文字遮罩截斷技術完整解析:CSS Mask 與漸層的雙層方案
前言
在設計應用商店、評論區等需要文字截斷的場景時,開發者往往面臨一個看似簡單但實則複雜的問題:如何優雅地在有限空間內展示文字,同時提供視覺流暢的「展開」體驗?
Apple 在其產品中採用了兩種層次分明的截斷技術,每種技術都針對不同的應用場景優化。本文將深入分析這兩種方案的實現原理、應用場景,以及各自的優劣勢。
第一部分:技術層面的雙層方案
方案一:簡化型截斷(multiline-clamp)
應用場景:App 商店描述
這是你在 Duolingo App Store 描述頁面看到的方案。
HTML 結構:
<p class="svelte-1up5qog"> <div class="multiline-clamp svelte-1a7gcr6" role="text"> <span class="multiline-clamp__text svelte-1a7gcr6"> Learn a new language with the world's most-downloaded education app! Duolingo is the fun, free app... </span> </div> <button class="svelte-1up5qog">more</button></p>CSS 實現:
button.svelte-1up5qog { --gradient-direction: 270deg; bottom: 0; display: flex; inset-inline-end: 0; /* 靠右邊 */ justify-content: end; position: absolute;
/* 漸變蓋層 - 從右下角開始淡出 */ background: linear-gradient( var(--gradient-direction), /* 270deg */ var(--pageBg) 72%, /* 72% 處是背景色 */ transparent 100% /* 100% 完全透明 */ );
color: var(--keyColor); padding-inline-start: 20px;}特徵:
- 結構簡潔,沒有複雜 mask 計算
- 只用一層
linear-gradient蓋層 - 對內容變動的容忍度高
方案二:高級型截斷(with-more-button)
應用場景:用戶評論區
這是用於 Duolingo 用戶評論的方案,需要處理各種長度和語言的內容。
HTML 結構:
<div class="truncate-wrapper svelte-1ji3yu5"> <p data-testid="truncate-text" class="content svelte-1ji3yu5 with-more-button" style="--lines: 5; --line-height: var(--lineHeight, 16); --link-length: 4;"> I have been using Duolingo consistently... </p> <button data-testid="truncate-more-button" class="more svelte-1ji3yu5" type="button"> MORE </button></div>CSS 實現(完整版):
.with-more-button.svelte-1ji3yu5 { --fade-direction: 270deg;
/* 雙層 mask 遮罩 */ mask: /* 第一層:水平遮罩(掐掉頂部) */ linear-gradient( 0deg, /* 垂直方向 */ transparent 0, transparent calc(var(--line-height) * 1px), /* 頂部透明 */ #000 calc(var(--line-height) * 1px) /* 以下顯示 */ ), /* 第二層:右下角漸隱 */ linear-gradient( var(--fade-direction), /* 270deg = 右下 */ transparent 0, transparent calc( var(--link-length) * var(--one-ch, 8) * 1px + var(--inline-mask-offset, 0px) ), #000 calc( (var(--link-length) * var(--one-ch, 8) + var(--line-height) * 2) * 1px + var(--inline-mask-offset, 0px) ) );
mask-position: right bottom; mask-size: initial, initial;
position: relative; word-break: break-word; z-index: var(--z-default);}CSS 變數說明:
| 變數 | 用途 | 典型值 |
|---|---|---|
--lines | 顯示行數 | 5 |
--line-height | 單行高度 | 16 |
--link-length | ”MORE” 按鈕寬度(字符數) | 4 |
--one-ch | 單個字符的像素寬度 | 8 |
--inline-mask-offset | 額外偏移 | 0px |
--fade-direction | 漸隱方向 | 270deg |
工作原理:
-
第一層漸變(0deg) - 垂直掐頂
transparent 0→transparent calc(var(--line-height) * 1px):頂部完全透明#000:下面部分完全不透明- 用途:確保文字從第一行開始顯示
-
第二層漸變(270deg) - 右下角淡出
- 從
transparent漸變到#000 - 距離由
--link-length和--one-ch決定 - 用途:在 “MORE” 按鈕位置漸隱文字
- 從
特徵:
- 複雜的 mask 計算,需要精確的像素級控制
- 通過 CSS 變數實現動態調整
- 適應不同內容長度和語言的文字寬度
- 視覺效果流暢精美
第二部分:兩種方案的對比
技術對比
| 維度 | 簡化型(App 描述) | 高級型(用戶評論) |
|---|---|---|
| 實現方式 | 單層 gradient 蓋層 | 雙層 mask 遮罩 |
| CSS 複雜度 | 低 | 高 |
| 計算量 | 最小 | 中等 |
| 瀏覽器支援 | 優 | 中(mask 支援略少) |
| 視覺精度 | 基礎 | 精確 |
| 代碼行數 | ~10 行 | ~30 行 |
功能對比
| 維度 | 簡化型 | 高級型 |
|---|---|---|
| 內容來源 | 靜態、可控 | 動態、用戶生成 |
| 長度變動性 | 低 | 高 |
| 語言適應 | 有限 | 強 |
| 漸隱效果 | 簡單覆蓋 | 精美漸隱 |
| 按鈕位置 | 固定 | 動態調整 |
| 移動端表現 | 穩定 | 穩定 |
第三部分:應用場景分析
簡化型方案何時使用?
✅ 最適合:
- App 商店描述(官方內容,相對固定)
- 營銷文案區域(控制度高)
- 內容管理系統的摘要(字數可控)
- SEO 重要區域(需要爬蟲容易解析)
- 低性能設備(減少計算)
❌ 不適合:
- 用戶評論(長度不一)
- 社交媒體動態(語言多樣)
- 多語言內容(字符寬度差異大)
高級型方案何時使用?
✅ 最適合:
- 用戶評論區(內容完全動態)
- 社交媒體動態(需要精美截斷)
- 多語言應用(需要自適應)
- 搜索結果摘要(字數不確定)
- 聊天記錄預覽(長度變動大)
❌ 不適合:
- 簡單的固定內容(過度設計)
- 低端 Android 設備(mask 支援問題)
- 對性能敏感的區域(計算複雜)
第四部分:深層設計思想
Apple 的設計哲學
Apple 在這個問題上展現的不是「一種解決方案」,而是「場景匹配的方案選擇」:
應用層次分析│├─ 靜態內容層│ ├─ 來源:官方│ ├─ 變動:低│ └─ 方案:簡化型 ✓ 快速、高效│└─ 動態內容層 ├─ 來源:用戶 ├─ 變動:高 └─ 方案:高級型 ✓ 精確、自適應核心優化點
1. 效能優化
- 不是所有截斷都需要複雜 mask
- 根據內容動態性選擇方案
- 減少不必要的 GPU 計算
2. 體驗優化
- 簡化型:快速載入,不影響頁面性能
- 高級型:精美漸隱,提升內容質感
3. 可維護性
- CSS 變數參數化,不需要改動 HTML
- 通過 Style Attribute 動態注入值
- 易於在後端控制截斷邏輯
4. 相容性考量
- 簡化型:廣泛支援(IE 都支援 gradient)
- 高級型:現代瀏覽器優先(mask 支援主要是 Chrome、Safari)
第五部分:實現細節深度剖析
mask 工作原理
CSS mask 使用漸變來控制元素的透明度。對於 with-more-button 的雙層 mask:
視覺效果分解:
原始文本(5 行):┌─────────────────────┐│ Line 1 ││ Line 2 ││ Line 3 ││ Line 4 ││ Line 5 more button │└─────────────────────┘
第一層 mask(垂直):┌─────────────────────┐│ ░░░░░░░░░░░░░░░░░░░ │ ← 透明(line-height 高度)│ ███████████████████ │ ← 不透明│ ███████████████████ ││ ███████████████████ ││ ███████████████████ │└─────────────────────┘
第二層 mask(右下漸隱):┌─────────────────────┐│ ███████████████████ ││ ███████████████████ ││ ███████████████████ ││ ███████░░░░░░░░░░░░ │ ← 漸變區│ ░░░░░░░░░░░░░░░░░░░ │ ← 完全透明└─────────────────────┘
最終效果(兩層疊加):┌─────────────────────┐│ ░░░░░░░░░░░░░░░░░░░ │ ← 第一層切掉│ ███████████████████ ││ ███████████████████ ││ ███████░░░░░░░░░░░░ │ ← 第二層淡出│ ░░░░░░░░░░░░░░░░░░░ │ ← 完全隱藏└─────────────────────┘CSS 變數的動態計算
關鍵計算式分解:
/* "MORE" 按鈕寬度的像素值 */var(--link-length) * var(--one-ch, 8) * 1px= 4 * 8 * 1px= 32px
/* 漸隱終止位置 */(var(--link-length) * var(--one-ch, 8) + var(--line-height) * 2) * 1px= (32px + 16px * 2)= 64px/* --link-length * --one-ch = 按鈕寬度 --line-height * 2 = 兩行高度(預留空間) 總共:按鈕寬度 + 預留空間*/第六部分:實踐建議
實施檢查清單
選擇方案時:
- 內容是靜態還是動態?
- 字符寬度是否一致(單語言 vs 多語言)?
- 性能要求有多高?
- 需要支援的最低瀏覽器版本?
- 視覺要求(基礎截斷 vs 精美漸隱)?
實施時:
- 簡化型:直接使用 gradient 蓋層,5 分鐘搞定
- 高級型:
- 測試 CSS 變數計算的準確性
- 驗證中文、日文等多字節文字的表現
- 測試極長內容(>1000 字)的表現
- 檢查移動端點擊 “MORE” 按鈕的可點擊區域
調試時:
/* 臨時添加背景色便於視覺調試 */.with-more-button { background: rgba(0, 0, 0, 0.1); /* 看清 mask 邊界 */}
button.more { background: rgba(255, 0, 0, 0.3); /* 看清按鈕邊界 */}第七部分:常見問題排查
Q1:多語言下 mask 計算不准?
原因: --one-ch 針對英文優化(8px),但中文/日文通常是 16px
解決:
.with-more-button { --one-ch: 8px; /* 英文、拉丁文 */}
.with-more-button[lang="zh"],.with-more-button[lang="ja"],.with-more-button[lang="ko"] { --one-ch: 16px; /* 東亞文字 */}Q2:Safari 上 mask 不生效?
原因: Safari 需要 -webkit- 前綴
解決:
.with-more-button { -webkit-mask: /* 雙層漸變 */; mask: /* 雙層漸變 */;}Q3:按鈕位置在不同分辨率下錯位?
原因: 硬編碼的 --one-ch 不適應 Retina 屏或縮放
解決:
@media (-webkit-min-device-pixel-ratio: 2) { .with-more-button { --one-ch: 16px; /* 調整 DPR */ }}第八部分:性能考量
方案一(簡化型)性能指標
首屏時間:最小化(無複雜計算)重排(Reflow):低重繪(Repaint):低GPU 使用:最小總體評分:⭐⭐⭐⭐⭐ 優秀方案二(高級型)性能指標
首屏時間:稍微增加(mask 計算)重排(Reflow):低重繪(Repaint):中(mask 更新時)GPU 使用:中(mask 需要 GPU 加速)總體評分:⭐⭐⭐⭐ 良好優化建議
- 使用
will-change優化
.with-more-button { will-change: mask; /* 提示瀏覽器預先優化 */}- 避免頻繁改動 CSS 變數
// ❌ 不要這樣做element.style.setProperty('--line-height', '16px');element.style.setProperty('--link-length', '4');
// ✅ 這樣做更好element.style.cssText = ` --line-height: 16px; --link-length: 4;`;- 使用 CSS Containment
.truncate-wrapper { contain: layout style paint; /* 限制重排範圍 */}第九部分:結論與延伸思考
核心發現
- 沒有絕對完美的方案 — 只有「適合當前場景的方案」
- 複雜性和成果成正比 — 投入得越多,效果越精美
- 效能和體驗需要平衡 — Apple 的做法就是這種平衡的體現
Apple 的啟示
即使是大公司的產品,也不是全部用最複雜的技術。而是根據:
- 📊 內容特性(靜態 vs 動態)
- 🎯 用戶體驗(轉化率 vs 視覺質感)
- ⚡ 性能預算(首屏時間 vs 計算成本)
來精心選擇解決方案。
延伸應用
這個思路可以推廣到:
- 文字行數限制(多行省略)
- 圖片自適應裁切
- 響應式排版
- 動態容器適配
都可以通過「靜態方案 + 動態方案」的雙層設計來優化。
附錄:完整代碼示例
完整的簡化型實現
<style> .app-description { position: relative; padding-bottom: 40px; }
.app-description.truncated { max-height: 150px; overflow: hidden; }
.app-description button { position: absolute; bottom: 0; right: 0; background: linear-gradient( 270deg, var(--page-bg, white) 72%, transparent 100% ); color: var(--key-color, #0066cc); padding: 0 20px; border: none; cursor: pointer; }</style>
<div class="app-description truncated"> <p>Learn a new language...</p> <button>more</button></div>完整的高級型實現
<style> .comment-container { --lines: 5; --line-height: 16; --link-length: 4; --one-ch: 8; }
.comment-text { mask: linear-gradient( 0deg, transparent 0, transparent calc(var(--line-height) * 1px), #000 calc(var(--line-height) * 1px) ), linear-gradient( 270deg, transparent 0, transparent calc( var(--link-length) * var(--one-ch) * 1px ), #000 calc( (var(--link-length) * var(--one-ch) + var(--line-height) * 2) * 1px ) ); -webkit-mask: var(--mask-value); mask-position: right bottom; mask-repeat: no-repeat; position: relative; }
.comment-more-btn { position: absolute; bottom: 0; right: 0; background: var(--page-bg, white); color: var(--key-color, #0066cc); padding: 0 20px; border: none; cursor: pointer; }</style>
<div class="comment-container"> <p class="comment-text">I have been using Duolingo...</p> <button class="comment-more-btn">MORE</button></div>參考資源
Apple 文字遮罩截斷技術完整解析:CSS Mask 與漸層的雙層方案
https://laplusda.com/posts/apple-text-mask-analysis/ 這篇文章有幫助嗎?
回報錯字、失效連結,或告訴我你想看的延伸主題。