展示螢幕
循環播放的全螢幕頁面,用在活動大螢幕、店面櫥窗或展場。它的設計目標是無人看管連續運作多天,限制見文末的「已知限制」。和簡報一樣,它是一個離線 HTML 檔案。
規則為什麼是這樣,以及出處:程式碼儲存庫的 plans/research-screen.md。
做一個展示螢幕
npx slidehtml screen new my-hall # 產生 src/screens/my-hall/index.tsx
npx slidehtml screen dev my-hall # 預覽
npx slidehtml screen check my-hall # 字型、型別檢查、建置、QA,最後一行是 PASS 或 FAIL
npx slidehtml screen build my-hall # 輸出 dist/screen/my-hall.html
寫完要跑到 PASS,並看過 qa/screen/my-hall/ 裡的每張圖。
check 需要 Playwright 的 Chromium(npx playwright install chromium)。其中的字型步驟需要 Python 3、fonttools、brotli 和 Noto CJK 的 .ttc 檔案(apt install fonts-noto-cjk fonts-noto-cjk-extra,或設定 NOTO_CJK_DIR)。缺少這些時,check 會以 FAIL 結束。build 不跑字型步驟,也不跑 QA,所以要在 check 之後用,不能取代它。
import { BigNumber, Hero, defineScreen } from '../../screen'
export const screen = defineScreen({
title: '我的展場',
theme: 'cobalt',
face: 'editorial', // editorial | grotesk | heavy
atmosphere: 'stars', // stars | grid | none
orientation: 'landscape', // landscape | portrait | both
venue: { diagonalIn: 98, distanceM: 5, baselineM: 1.4, panel: 'lcd' },
clock: { date: true },
reloadAt: '04:30',
scenes: [
{ id: 'open', label: '開場', seconds: 12, render: () => <Hero title={['一個訊息']} source="來源:…" /> },
],
})
字級由場地決定
venue 必填。寫明面板大小(diagonalIn,LED 牆用 longEdgeMm)、希望從多遠讀到(distanceM)、文字離地多高(baselineM)。引擎據此算出所有字級,所以沒有哪個範本能設定字級。欄位放不下,就刪字或拆場景。
QA 測量字形的真實高度:
| 文字 | 規則 |
|---|---|
| 拉丁字母、數字 | 2010 ADA Standards Table 703.5.5,按大寫 I 測量 |
| 中文 | 同一數值 × 1.54(Zhang et al. 2020) |
| 小字(來源、署名) | 在 nearM(預設 1.5 公尺)處 16 弧分,且至少 20 設計 px |
舉例:98 吋面板從 5 公尺讀,中文字級約 80 設計 px。5 公尺寬的 LED 牆從 10 公尺讀、安裝位置較高時,約 77 設計 px。
範本
每個範本都有自己的橫向和直向版式。下表的上限針對範例場地,真正的上限以你的場地下 QA 的結果為準。
| 範本 | 用途 | 欄位 |
|---|---|---|
Hero | 一個訊息 | kicker?、title[](≤ 3 行)、portraitTitle[]?、sub?、art?、source、credit? |
BigNumber | 一個數字 | kicker?、value(≤ 7 個字元)、unit?、label、note?、art?、source、credit? |
Moments | 3–5 個事件,依序點亮 | kicker?、title[]、items[{ when, at?, label }]、source |
Quote | 某人說過的一句話 | kicker?、lines[]、original?、by、source |
Pair | 兩半,後一半是重點 | kicker?、items[2]{ big, small }、source |
Board | 固定的小表格 | kicker?、title、rows[{ left, right }](≤ 5)、source |
NowNext | 正在進行和下一場,由時鐘決定 | source |
Agenda | 今日議程,由時鐘決定 | title?、source |
art 寫成 { src, mode: 'disc' | 'ground' },src 是用 import 匯入的 decor- 或 photo- 圖片,並在 credit 裡寫明。
中文可以在任意兩個字之間換行,所以較長的 sub 或 note 可能把詞組拆開。要保持完整,用 JS 字串寫換行:sub={'第一句,\n第二句。'}。一般的 sub="..." 屬性裡,JSX 會把 \n 當成字面文字。議程標題來自資料,不能寫換行,要寫得夠短,或檢查直向的 NowNext,那裡會以最大字級換行。
不要嵌入 kit 投影片。它們的內文字級是給坐在室內的人設計的,在任何真實的展示場地都過不了距離規則。
內容規則
- 一個場景一個訊息。三個數字就是三個場景。
source必填,以小字顯示。查不到出處的事實不要寫。- 虛構資料:在螢幕上設定
sample。它以內文字級顯示在每個場景上,任何場景少了它 QA 都會 FAIL。 seconds要夠讀完:2 秒 + 字元數 ÷ 每分鐘 260 + 單字數 ÷ 每分鐘 238,最少 6 秒。
時間
timezone 是 IANA 名稱,不寫就用播放裝置自己的時區。下面的內容都跟隨它。
{ id: 'lunch', label: '午餐', seconds: 9, when: { from: '12:00', to: '13:30' }, render: … } // 只在這段時間播放
{ when: { from: '22:00', to: '02:00' } } // 跨午夜
{ when: [{ from: '09:00', to: '18:00', days: [1, 2, 3, 4, 5] }] } // 平日;0 是週日
{ when: { from: '08:00', to: '20:00', dates: ['2026-11-14'] } } // 某一天
要顯示正在進行和下一場,寫 agenda: (data) => sessions。每場有 start、end、title、who?、room?、date?、isBreak?。NowNext、Agenda 和 items 為函式的 ticker 都跟隨時鐘,不需要重新載入。states 列出 QA 要拍照的時刻。
即時資料
data: { initial: SAMPLE, parse: parseEvent, everySec: 30, staleAfterSec: 120, source: 'event-data.js' }
設定 source,或在 URL 裡寫 ?data=。
- 本機檔案必須是放在頁面旁邊的
.js檔案,用相對路徑。內容是一次呼叫:window.__screenData({ ... })。從file://開啟的頁面讀不了 JSON。 - URL 必須回傳 JSON,如果頁面在
file://上,還要允許 CORS。遠端.js會被拒絕,因為它會在頁面裡當成程式碼執行。
parse 遇到格式錯誤的內容應該拋出例外:上一份正確的資料會保留,重新載入後也還在。超過 staleAfterSec 沒有拿到正確資料時,螢幕上顯示 数据已过期 · 最后更新 14:32,資料恢復後消失。
安裝
把這一個 HTML 檔案複製到播放裝置,然後:
# Linux 或樹莓派。參數來自樹莓派的 kiosk 教學。
chromium --kiosk --noerrdialogs --disable-infobars --no-first-run --start-maximized \
"file:///home/signage/my-hall.html"
瀏覽器之外必須有一個監控程式。 頁面能接住自己的錯誤,但重新啟動不了當掉的算繪程序,也發現不了自己卡住的主執行緒。下面的 unit 只是起點,沒有在真實的播放裝置上跑過;使用者服務還需要你的圖形工作階段環境,所以活動之前要測試,包括拔電源。
# /etc/systemd/user/signage.service
[Service]
ExecStart=/usr/bin/chromium --kiosk --noerrdialogs --disable-infobars --no-first-run --start-maximized file:///home/signage/my-hall.html
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
裝置上還要做這些:
- 在作業系統裡關閉螢幕變黑和 DPMS,關閉面板內建的省電計時器。頁面會請求 wake lock,但瀏覽器只把它當成建議。
- 把面板的畫面尺寸設為 1:1 / Just Scan / 不過掃描。
- 讓系統時鐘透過 NTP 同步。排程準不準,取決於裝置時鐘準不準。
- 樹莓派要開啟 overlay 檔案系統,這樣斷電不會損壞記憶卡。
- 直立面板:盡量在作業系統裡旋轉。做不到的話,在 URL 加
?rotate=90或?rotate=270。面板可以往哪邊轉,查說明書,各廠商不一樣。
操作按鍵
不按鍵時什麼都不顯示。滑鼠游標是隱藏的。
| 按鍵 | 作用 |
|---|---|
F | 全螢幕 |
Space | 暫停,10 分鐘後自動繼續 |
N / →,P / ← | 下一個,上一個 |
I | 狀態:時鐘、運作時間、記憶體、wake lock、資料、錯誤記錄 |
R | 重新載入 |
URL 參數
| 參數 | 意思 |
|---|---|
?now=14:30,?now=2026-11-14T14:30 | 讓螢幕時鐘假定現在是這個時間 |
?speed=60 | 讓時間跑快,用來預覽 |
?pin=<scene id> | 停在一個場景上 |
?rotate=90 | 在沒有旋轉的輸出上旋轉畫面 |
?data=<路徑或 url> | 資料來源 |
出問題時會怎樣
| 故障 | 行為 |
|---|---|
| 場景拋出例外或什麼都沒算繪 | 記錄並跳過,不會黑畫面。休息 5 分鐘,加倍直到 1 小時,然後再試 |
| 所有場景都失敗,或沒有場景在排程內 | 顯示帶標題的等待卡,每 30 秒重試 |
| 找不到責任場景的錯誤 | 記錄,循環繼續 |
| 資料輪詢失敗或格式錯誤 | 上一份正確的資料保留。退避到 10 分鐘一次。到設定時間後顯示過期提示 |
堆積記憶體超過 memoryLimitMb(預設 400) | 在下一次場景切換時重新載入,30 分鐘內最多一次。僅限 Chromium |
reloadAt | 每天在下一次場景切換時重新載入一次。透過 http 提供時,只有 HEAD 檢查成功才會載入 |
| 算繪程序當掉或卡住,或機器重新開機 | 頁面無能為力:這是監控程式的工作 |
烙印
底色接近純黑,不用純白。時鐘、名稱和來源每次切換場景時移動幾個像素,每 10 分鐘左右互換(burnIn: { flipMinutes, shift })。panel: 'oled' 把移動幅度加倍,'led' 關閉移動。如果完整循環一遍後,畫面上有超過 1% 的區域始終高於 25% 亮度,QA 會 FAIL。這只能降低風險,不能消除風險。例如 LG 的 B2B 保固不包含殘影。請讀你自己的保固條款。
QA 檢查什麼
scripts/screen-qa.mjs 針對建置好的檔案執行,按 orientation 在 1920×1080 和 3840×2160,和/或 1080×1920 和 2160×3840 上檢查:
溢位、裁切、重疊和 90% 安全區。按字形檢查文字大小是否符合場地。相對真實像素的對比度至少 4.5:1。退回系統字型。來源是否存在,範例標註。每個場景的時間。每個時間區段的邊界和即時時鐘換日。場景拋出例外、在 effect 裡失敗、什麼都沒算繪。所有場景失敗,然後恢復。非同步錯誤。操作按鍵。資料來源、錯誤資料、檔案遺失、過期狀態、恢復、斷線期間重新載入、遠端指令碼。夜間重新載入和記憶體重新載入。烙印像素。800 次場景切換的 soak 測試,堆積記憶體、DOM 或監聽器無限增長時 FAIL。沒有主控台錯誤和網路請求。
已知限制
- soak 測試是把時間加速跑 800 次場景切換,不等於在裝置上跑幾天,也測不到 GPU 記憶體、驅動程式和面板。活動之前,在真實的播放裝置上跑幾天。
- 頁面重新啟動不了當掉的算繪程序,也發現不了自己卡住的主執行緒,所以瀏覽器之外必須有監控程式。上面的 systemd unit 只是起點,沒有在真實的播放裝置上跑過。
- wake lock 只是建議。裝置上也要關閉螢幕變黑和省電。
- 排程準不準,取決於裝置時鐘準不準。
- 記憶體重新載入只在 Chromium 裡有效。
- 防烙印措施只能降低風險,不能消除風險。
- 資料過期和暫停的提示只有簡體中文,寫了
lang: 'en'也一樣。