電商購物體驗

直播購物疊層

直播賣場 UI 疊層的 React 元件:固定比例舞台上疊 LIVE 徽章與觀看人數滾動、愛心噴發、彈幕留言流、「+1」喊單自動偵測與浮出計數、置頂商品卡與庫存倒數,可放 children 當真正的影片。

這是 WebberUI Pro 元件

下方的安裝指令需要授權才能執行,未授權時會回 401。

怎麼安裝 Pro 元件 →查看方案 →

台灣社群直播、電商直播的「喊單」文化:主播介紹商品,觀眾在留言區打「+1」就代表下單。這個元件把整套直播賣場的 UI 疊層封裝成一個固定比例的舞台(直式 9:16 或橫式 16:9)——底層是漸層+緩慢漂移的光暈當作影片占位(也可放 children 當真正的 video),上面疊:左上 LIVE 徽章與逐位滾動的觀看人數、右側愛心按鈕與上飄粒子、左下留言流(新訊息從底部滑入、舊的上移淡出、帳號以色塊首字當頭像)、+1 自動偵測(符合規則的留言高亮,商品卡上方浮出「+1」字樣,訂單數與庫存即時更新)、置頂商品卡與「+1 喊單」按鈕。留言串支援受控/非受控,也可以開 simulate 讓元件自己模擬留言與觀看人數變動;所有帳號、留言與商品皆為虛構示範資料。

把這個效果裝進你的專案

在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。

npx webberui@latest add "https://webberui.com/r/live-shopping-overlay.json?t=<安裝 token>"

還沒有專案,或安裝遇到問題?看快速開始 →

Live Shopping Overlay 是用 React、Tailwind CSS 與 Motion 做的直播購物(直播帶貨、直播喊單)介面疊層元件,圖示來自 lucide-react。它只負責畫面與 +1 判定,留言來源、訂單建立與付款流程都由你的後端處理。

Playground

即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。

5
編輯中的呼叫預設屬性
<LiveShoppingOverlay/>

完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。

適用情境

  • 電商平台或品牌官網的直播賣場頁,疊在串流播放器上
  • 社群團購、直播喊單的後台預覽,讓主播在開播前確認畫面
  • 直播電商產品的提案、展示頁,用 simulate 呈現開播後的樣子
  • 短影音或直播 App 原型,在網頁上驗證直式 9:16 的互動配置

安裝

npx webberui@latest add "https://webberui.com/r/live-shopping-overlay.json?t=<安裝 token>"

或在 components.json 設定 registries 後,改用 @webberui/live-shopping-overlay 安裝。

使用

import {
  LiveShoppingOverlay,
  isPlusOne,
} from "@/components/ui/live-shopping-overlay";

// 示範模式:留言流與觀看人數由元件內部模擬(商品與帳號皆為虛構)
<LiveShoppingOverlay
  simulate
  product={{ name: "陶瓷手沖濾杯組", price: 680, stock: 12 }}
  onPlusOne={(msg) => console.log("喊單", msg.user, msg.text)}
  onHeart={(count) => console.log("愛心", count)}
/>

// 正式接後端:留言受控、觀看人數由 API 提供,舞台底層放真正的 video
<LiveShoppingOverlay
  layout="landscape"
  messages={messages}
  onMessagesChange={setMessages}
  viewers={viewers}
  product={product}
  onPlusOne={(msg) => createOrder(msg)}
>
  <video src={streamUrl} autoPlay muted playsInline />
</LiveShoppingOverlay>

// 喊單判定函式可單獨使用(例如在後端或表單層過濾留言)
isPlusOne("+1 黑色"); // true
isPlusOne("我要 1 個"); // true
isPlusOne("有優惠碼嗎"); // false

範例

用 WebSocket 接即時留言

受控模式下,新留言由你推進 messages;按「+1 喊單」時元件會呼叫 onMessagesChange 並帶入加上新留言的完整陣列,最後一則就是自己送出的留言。

"use client";

import * as React from "react";
import {
  LiveShoppingOverlay,
  type LiveMessage,
} from "@/components/ui/live-shopping-overlay";

export function LiveRoom({ roomId }: { roomId: string }) {
  const [messages, setMessages] = React.useState<LiveMessage[]>([]);

  React.useEffect(() => {
    const ws = new WebSocket(`wss://example.com/rooms/${roomId}`);
    ws.onmessage = (event) => {
      const msg: LiveMessage = JSON.parse(event.data);
      setMessages((prev) => [...prev, msg].slice(-50));
    };
    return () => ws.close();
  }, [roomId]);

  return (
    <LiveShoppingOverlay
      messages={messages}
      onMessagesChange={(next) => {
        const mine = next[next.length - 1];
        setMessages(next);
        void fetch(`/api/rooms/${roomId}/comments`, {
          method: "POST",
          body: JSON.stringify(mine),
        });
      }}
      product={{ name: "保溫隨行杯 500ml", price: 890, stock: 30 }}
      onPlusOne={(msg) => console.log("新喊單", msg.user)}
    />
  );
}

id 必須唯一,元件以它判斷新留言;同一個 id 不會重複計入喊單。

橫式舞台搭配真正的影片

layout="landscape" 改成 16:9;children 會被拉伸填滿並放在所有疊層下方。

<LiveShoppingOverlay
  layout="landscape"
  title="週五晚間保養品專場"
  selfName="小安"
  showViewers={false}
  maxMessages={4}
  messages={messages}
  onMessagesChange={setMessages}
  product={product}
>
  <video src={streamUrl} autoPlay muted playsInline />
</LiveShoppingOverlay>

用 isPlusOne 統計留言

具名匯出的 isPlusOne 和元件內部使用同一套規則,可以在其他客戶端元件裡統計喊單數。

import { isPlusOne } from "@/components/ui/live-shopping-overlay";

const plusOneCount = messages.filter((m) => isPlusOne(m.text)).length;

Props

Prop型別預設值說明
messagesLiveMessage[]—受控留言串(由舊到新);提供時內部狀態與 simulate 失效,新增留言請透過 onMessagesChange 回寫
defaultMessagesLiveMessage[][]非受控模式的初始留言
onMessagesChange(messages: LiveMessage[]) => void—留言串變動時回呼(按「+1 喊單」或模擬新增留言);受控模式下由此更新 messages
simulatebooleanfalse非受控時由元件內部以計時器模擬留言流(含隨機 +1);示範用,正式接後端時請關閉
productLiveProduct內建虛構商品(NT$680、庫存 20)置頂商品(名稱、售價、起始庫存)
viewersnumber—受控觀看人數;未提供時由內部模擬每隔數秒微幅浮動
defaultViewersnumber1280內部模擬觀看人數的起始值(viewers 未提供時才有效)
showViewersbooleantrue是否顯示右上角觀看人數
titlestring"週三晚上開賣"直播間標題,顯示於 LIVE 徽章右側
selfNamestring"我"按「+1 喊單」時送出留言所用的帳號名稱
onPlusOne(message: LiveMessage) => void—每偵測到一則新的 +1 留言時回呼一次(含自己按下的喊單)
onHeart(count: number) => void—點擊愛心時回呼,帶入累計次數
layout"portrait" | "landscape""portrait"舞台比例:直式 9:16(手機直播)或橫式 16:9
maxMessagesnumber5留言流最多同時顯示的則數,超過的舊留言上移淡出
childrenReact.ReactNode—放在舞台底層的節點(例如真正的 video),會被拉伸填滿並置於疊層之下
classNamestring—透傳到最外層舞台容器

LiveMessage

欄位型別預設值說明
idstring—唯一識別碼;元件以此判斷「新訊息」,同一個 id 不會重複觸發 +1
userstring—發言帳號名稱(頭像取首字,並依名稱雜湊上色)
textstring—留言內容;符合 +1 規則時自動高亮並計入喊單
tsnumber—送出時間(毫秒時間戳),選填、僅供呼叫端排序或顯示

LiveProduct

欄位型別預設值說明
namestring—商品名稱
pricenumber—售價(新台幣)
stocknumber—起始庫存;每一筆 +1 扣一件,扣到 0 顯示完售並停用喊單按鈕

另外具名匯出 isPlusOne(text) 與 LiveShoppingLayout 型別,方便在後端或表單層重複使用同一套喊單判定規則。

細節

  • +1 判定規則(isPlusOne):留言 trim 後符合 /(^|\s)\+?1(\s|$)/(獨立的「1」或「+1」,例如「1」「我要 1 個」),或內文任何位置含「+1」/全形「+1」(例如「+1黑色」「幫我留一個+1」)
  • 訂單數與庫存:每一則「新的」+1 留言扣一件庫存並觸發 onPlusOne;元件以 id 記錄已處理的留言,同一個 id 不會重複計數。初始留言(defaultMessages 或首次傳入的 messages)中的 +1 直接算進起始訂單數,但不觸發回呼與浮出動畫,SSR 與客戶端輸出一致
  • 受控/非受控:提供 messages 即為受控,元件不再自己新增留言(simulate 失效),按「+1 喊單」會以 onMessagesChange([...messages, 新留言]) 回寫,由你決定是否送到後端。非受控時內部保留的則數比 maxMessages 多一些,動態調大上限還有舊留言可補
  • 模擬留言流:以 setTimeout 鏈而非固定 setInterval 排程,每則間隔帶確定性抖動,讀起來像真人留言而非節拍器;約三成五機率喊單,完售後只出一般聊天;分頁切到背景時不塞留言,避免回來瞬間一大串動畫同時播放;卸載時清理計時器
  • 觀看人數:viewers 受控或以 defaultViewers 起始由內部每 3 秒微幅浮動(約 ±3%),數字逐位滾動;千分位格式自行實作,不走 toLocaleString,避免伺服器與瀏覽器 ICU 差異造成 hydration 不一致
  • 愛心與「+1」浮出粒子皆以 motion 自行實作,每顆的飄移、擺幅、縮放與色調由序號推導的確定性偽亂數算出,render 期間不呼叫 Math.random;粒子壽命結束即移除並清理計時器
  • 底層「影片」占位是漸層+兩顆緩慢漂移的光暈,不用外部影片/圖片;傳入 children 時會被拉伸填滿(object-cover)並置於所有疊層之下,上下漸暗遮罩確保白字、留言與商品卡在任何底圖上都可讀
  • 庫存倒數:商品卡進度條隨每筆 +1 縮短,剩 3 件以內轉紅並顯示「最後 N 件」,扣到 0 顯示「已完售」並停用喊單按鈕

可及性

  • 最外層為 role="region" 並以 aria-label 帶入直播間標題;愛心按鈕與「+1 喊單」皆為 type="button",具 focus-visible 環,完售時喊單按鈕 disabled
  • 留言流為 role="log" 但 aria-live="off"——模擬留言每一兩秒一則,逐則播報會淹沒螢幕閱讀器;只有自己按下「+1 喊單」時,才透過隱形的 role="status" 區域播報累計單數與剩餘庫存
  • 觀看人數不放 aria-live(每隔數秒就變),改以 sr-only 文字補上「人觀看中」單位;愛心按鈕的 aria-label 帶入已送出次數
  • 所有裝飾性元素(光暈、漸暗遮罩、頭像色塊、粒子、進度條)皆 aria-hidden,留言文字本身仍是可讀的 DOM 內容
  • 使用者系統開啟「減少動態效果」時,停用光暈漂移、LIVE 呼吸點、愛心與「+1」粒子、數字滾動與留言滑入動畫,只保留數字、高亮與文字更新;模擬留言與觀看人數變動的間隔放慢一倍

常見問題

元件會建立訂單或處理付款嗎?

不會。偵測到新的 +1 留言時只會呼叫 onPlusOne,訂單建立、庫存檢查與付款都需要在你的後端完成。

畫面上的庫存會與後端同步嗎?

不會自動同步。顯示的庫存是 product.stock 減去留言串中已計入的 +1 筆數,屬於畫面上的即時計數;實際能否成立訂單,請以後端回應為準。

什麼樣的留言算 +1?

留言 trim 後含有獨立的「1」或「+1」(例如「1」「我要 1 個」),或任何位置含有「+1」、全形「+1」(例如「+1黑色」)。規則可以透過 isPlusOne 單獨使用。

可以在 Next.js App Router 使用嗎?

可以。檔案開頭已經有 "use client",在 Server Component 頁面裡照一般方式 import 即可。初始留言中的 +1 會直接算進起始訂單數,觀看人數的千分位格式也是自行實作,伺服器與瀏覽器輸出一致。

simulate 可以用在正式環境嗎?

不建議。simulate 會用計時器產生虛構留言與觀看人數變動,只適合示範與原型;接後端時改用受控的 messages 與 viewers。