找回密碼
 立即註冊
惟家LINE群QRCODE
    查看: 3|回覆: 0

    SVG 平面圖的 CSS 動畫:燈漸亮、門轉開、風扇轉(完整範例)

    [複製鏈接]

    !lvup!   100%

    363

    主題

    27

    回帖

    2200萬

    積分

    管理員

    積分
    22005752
    發表於 3 天前 | 顯示全部樓層 |閱讀模式
    這篇是 SVG 版本真正的價值

    所有動畫都是純 CSS,不用寫 JavaScript



    先講一個關鍵觀念

    ha-floorplan 的工作只有一件事:依照狀態換 class。
    1. 燈開了  →  元素加上 class "light-on"
    2. 燈關了  →  元素加上 class "light-off"
    複製代碼

    剩下全部是 CSS 的事。

    所以你在網路上看到的任何 CSS 效果,理論上都能用在平面圖上。

    動畫一:燈漸亮(最基本也最實用)
    1. /* home.css */
    2. .light-off {
    3.   fill: #F4F6FA;
    4.   transition: fill 0.8s ease-in-out;
    5.   cursor: pointer;
    6. }
    7. .light-on {
    8.   fill: #FFD54F;
    9.   transition: fill 0.8s ease-in-out;
    10.   cursor: pointer;
    11. }
    複製代碼

    transition 那一行就是全部的魔法。

    它的意思是:「fill 這個屬性改變的時候,花 0.8 秒慢慢變過去」。

    兩個 class 都要寫 transition ——
    只寫在 .light-on 的話,亮的時候會漸變,暗的時候會瞬間跳掉

    動畫二:亮度對應真實亮度



    這個要用 style_set 而不是 class_set:
    1.   rules:
    2.     - entities:
    3.         - light.living_room
    4.       tap_action: toggle
    5.       state_action:
    6.         action: call-service
    7.         service: floorplan.style_set
    8.         service_data: >
    9.           ${(() => {
    10.             if (entity.state !== 'on') return 'fill: #F4F6FA; transition: all 0.8s;';
    11.             const b = entity.attributes.brightness || 255;
    12.             const pct = b / 255;
    13.             return 'fill: #FFD54F; fill-opacity: ' + (0.25 + pct * 0.75).toFixed(2)
    14.                  + '; transition: all 0.8s;';
    15.           })()}
    複製代碼

    效果:燈調到 30%,房間也只有 30% 黃。

    這個 JavaScript 看起來複雜,但邏輯很單純


    • 燈是關的 → 回傳灰色
    • 燈是開的 → 拿 brightness(0-255)算成百分比,套用到透明度


    brightness 在燈關著的時候是 undefined
    所以 || 255 的預設值不能省。

    動畫三:燈的光暈(用 SVG 濾鏡)

    這個效果很漂亮,而且只有 SVG 做得到。

    先在 SVG 裡加一個濾鏡定義(用文字編輯器打開 home.svg,
    <defs> 裡加):
    1. <defs>
    2.   <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
    3.     <feGaussianBlur stdDeviation="8" result="blur"/>
    4.     <feMerge>
    5.       <feMergeNode in="blur"/>
    6.       <feMergeNode in="SourceGraphic"/>
    7.     </feMerge>
    8.   </filter>
    9. </defs>
    複製代碼

    然後在 CSS 裡用它
    1. .light-on {
    2.   fill: #FFD54F;
    3.   filter: url(#glow);
    4.   transition: all 0.8s ease-in-out;
    5. }
    6. .light-off {
    7.   fill: #F4F6FA;
    8.   filter: none;
    9.   transition: all 0.8s ease-in-out;
    10. }
    複製代碼

    燈開的時候房間邊緣會有柔和的光暈。

    沒有 defs 區塊的話自己加一個,放在 <svg> 標籤後面就好。



    動畫四:門真的轉開(這個最有成就感)

    第一步:在 Inkscape 裡把門畫成一個細長方形


    • 門的寬度 = 門的厚度(例如 6px)
    • 門的長度 = 門的寬度(例如 80px)
    • 位置放在門框的鉸鏈那一側
    • id 設成 binary_sensor.front_door


    第二步:CSS
    1. .door-closed {
    2.   transform: rotate(0deg);
    3.   transform-origin: top left;
    4.   transform-box: fill-box;
    5.   transition: transform 0.6s ease-in-out;
    6.   fill: #8D6E63;
    7. }
    8. .door-open {
    9.   transform: rotate(-75deg);
    10.   transform-origin: top left;
    11.   transform-box: fill-box;
    12.   transition: transform 0.6s ease-in-out;
    13.   fill: #8D6E63;
    14. }
    複製代碼

    這裡有兩行是關鍵,而且是最多人卡住的地方

    transform-box: fill-box

    這一行沒寫的話,旋轉中心會是整個 SVG 畫布的原點
    門會飛到畫面外面

    加了之後,旋轉的參考框變成「這個元素自己的邊界框」。

    transform-origin: top left

    指定繞著哪個點轉。門是繞著鉸鏈轉的,所以是元素的一角。


    • 鉸鏈在左上 → top left
    • 鉸鏈在右上 → top right
    • 鉸鏈在左下 → bottom left


    試幾次就知道哪個對

    第三步:規則
    1.     - entities:
    2.         - binary_sensor.front_door
    3.       state_action:
    4.         action: call-service
    5.         service: floorplan.class_set
    6.         service_data: '${entity.state === "on" ? "door-open" : "door-closed"}'
    複製代碼

    門一開,SVG 上的門片就轉開 75 度。

    動畫五:風扇旋轉
    1. @keyframes spin {
    2.   from { transform: rotate(0deg); }
    3.   to   { transform: rotate(360deg); }
    4. }
    5. .fan-on {
    6.   animation: spin 1.2s linear infinite;
    7.   transform-origin: center;
    8.   transform-box: fill-box;
    9. }
    10. .fan-off {
    11.   animation: none;
    12. }
    複製代碼

    一樣要 transform-box: fill-box,不然會繞著畫布原點轉。

    讓轉速對應風速
    1.       state_action:
    2.         action: call-service
    3.         service: floorplan.style_set
    4.         service_data: >
    5.           ${entity.state === 'on'
    6.             ? 'animation: spin ' + (3 - (entity.attributes.percentage || 33) / 50)
    7.               + 's linear infinite; transform-origin: center; transform-box: fill-box;'
    8.             : 'animation: none;'}
    複製代碼



    動畫六:警報脈動
    1. @keyframes alert-pulse {
    2.   0%, 100% { opacity: 1;   fill: #E53935; }
    3.   50%      { opacity: 0.35; fill: #FF8A80; }
    4. }
    5. .alert-active {
    6.   animation: alert-pulse 1s ease-in-out infinite;
    7. }
    8. .alert-idle {
    9.   opacity: 0;
    10.   transition: opacity 0.5s;
    11. }
    複製代碼

    平常 opacity: 0(看不見),觸發時開始脈動。

    動畫七:房間有人時的柔和呼吸
    1. @keyframes occupied-breathe {
    2.   0%, 100% { fill-opacity: 0.15; }
    3.   50%      { fill-opacity: 0.35; }
    4. }
    5. .occupied {
    6.   fill: #4FC3F7;
    7.   animation: occupied-breathe 4s ease-in-out infinite;
    8. }
    複製代碼

    四秒一個循環,很慢很柔和 —— 你會感覺到「那個房間有人」,
    但不會覺得吵。

    一個完整的 CSS 檔案範本
    1. /* ========== 共用 ========== */
    2. svg, svg * {
    3.   vector-effect: non-scaling-stroke;
    4. }
    5. .clickable {
    6.   cursor: pointer;
    7. }
    8. /* ========== 燈光 ========== */
    9. .light-off {
    10.   fill: #F4F6FA !important;
    11.   transition: fill 0.8s ease-in-out, filter 0.8s;
    12.   filter: none;
    13.   cursor: pointer;
    14. }
    15. .light-on {
    16.   fill: #FFD54F !important;
    17.   transition: fill 0.8s ease-in-out, filter 0.8s;
    18.   filter: url(#glow);
    19.   cursor: pointer;
    20. }
    21. /* ========== 門窗 ========== */
    22. .door-closed, .door-open {
    23.   transform-origin: top left;
    24.   transform-box: fill-box;
    25.   transition: transform 0.6s ease-in-out;
    26.   fill: #8D6E63;
    27. }
    28. .door-closed { transform: rotate(0deg); }
    29. .door-open   { transform: rotate(-75deg); }
    30. /* ========== 風扇 ========== */
    31. @keyframes spin {
    32.   from { transform: rotate(0deg); }
    33.   to   { transform: rotate(360deg); }
    34. }
    35. .fan-on {
    36.   animation: spin 1.2s linear infinite;
    37.   transform-origin: center;
    38.   transform-box: fill-box;
    39. }
    40. .fan-off { animation: none; }
    41. /* ========== 警報 ========== */
    42. @keyframes alert-pulse {
    43.   0%, 100% { opacity: 1;    fill: #E53935; }
    44.   50%      { opacity: 0.35; fill: #FF8A80; }
    45. }
    46. .alert-active { animation: alert-pulse 1s ease-in-out infinite; }
    47. .alert-idle   { opacity: 0; transition: opacity 0.5s; }
    48. /* ========== 文字 ========== */
    49. .sensor-text {
    50.   font-family: 'Microsoft JhengHei', sans-serif;
    51.   font-size: 15px;
    52.   fill: #2C3440;
    53.   transition: fill 0.5s;
    54. }
    55. .sensor-hot  { fill: #E53935; }
    56. .sensor-cold { fill: #1E88E5; }
    複製代碼

    三個常見的除錯狀況

    一、元素飛到畫面外

    99% 是漏了 transform-box: fill-box。

    或者那個元素被群組包住了(群組的 transform 會干擾)。

    二、CSS 改了沒反應


    • 瀏覽器快取 → Ctrl+F5
    • 元素有行內 style → 加 !important,或去 Inkscape 刪掉那個屬性
    • class 名稱打錯 → 開 console_log_level 看實際套上了什麼 class


    三、動畫很卡


    • 同時在跑的 animation 太多
    • filter(光暈)很吃效能,只用在一兩個元素上
    • 在手機或舊平板上特別明顯


    ---

    下一篇:房間顏色隨溫度連續變化 ——
    這是 Picture Elements 完全做不到的效果。
    您需要登入後纔可以回帖 登入 | 立即註冊

    本版積分規則

    Archiver|手機版|惟家的智能論壇

    GMT+8, 2026-9-24 12:21 , Processed in 0.085835 second(s), 24 queries .

    快速回覆 返回頂部 返回列表