iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Modern Web

了不起的 Svelte系列 第 28

第 28 天:Svelte 的動態效果:`tweened`

  • 分享至 

  • xImage
  •  

第 28 天:Svelte 的動態效果:tweened

第 28 天要講的事

  1. 介紹 Svelte 的動態效果:tweened
  2. 利用 tweened 實作動態漸變色票

  我們先前花了三天的時間好好介紹了 Svelte 的 Store,為了要能夠讓資料有水平溝通的管道,或者為了讓一般 Javascript 的變數,能夠跟 Svelte 元件當中的變數有溝通的橋樑,Svelte 提供了 Store 這個方便的工具。我們不僅可以使用 Svelte 打造的最基本的 writable Store,同時還可以從 writable Store 出發,快速的客製化我們需要的 Store。
  以為 Svelte 只提供這麼樸素的 Store 嗎?在看過昨天第 27 天:Svelte 的過場:transition的文章之後,應該有發現到 Svelte 其實是個很花俏的傢伙了吧 😂 所以說,除了最基本的 writable Store 之外,Svelte 還另外提供了一個有趣的東西:具有動態效果的 Store。今天要介紹的,就是這個有趣的動態效果 Store:tweened

介紹 Svelte 的動態效果:tweened

  首先我們可以到 Svelte 的 REPL (Read-Eval-Print-Loop) 寫一段程式碼:

<script>
  import { onMount } from 'svelte';
  import { writable } from 'svelte/store';
  const progress = writable(0);

  onMount(() => {
    const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
    return () => clearInterval(interval);
  })
</script>

<progress value={$progress} />
  • 第四行:const progress = writable(0);
      初始化一個 writable Store,命名為 progress

  • 第七行:const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
      將 setInterval 放進 onMount 當中,並利用 setInterval 的功能,讓 progress 一秒一秒從 0 跳到 1,再從 1 跳回 0

  • 第十二行:<progress value={$progress} />
      並讓 progress 當中儲存變數的變化反應到 HTML 元素 <progress> 上。

https://i.ibb.co/TR6gVsZ/28.gif
圖一、我跳到 1 了!我跳回 0 了!我又跳到 1 了!我又跳回 0 了!

  可以這樣跳來跳去固然有趣,但是只有非 01<progress> 似乎稍嫌呆板。讓我們試試看具有動態效果的 Store tweened 看看吧:

<script>
  import { onMount } from 'svelte';
  import { tweened } from 'svelte/motion';
  const progress = tweened(0);

  onMount(() => {
    const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
    return () => clearInterval(interval);
  })
</script>

<progress value={$progress} />
  • 第三行:import { tweened } from 'svelte/motion';
      引入 tweened

  • 第四行:const progress = tweened(0);
      因為 tweened 其實也是一種 Store,使用方法就像 Store。首先初始化一個 tweened,並將其命名為 progress

  • 第七行:const interval = setInterval(() => $progress === 1? progress.set(0): progress.set(1), 1000)
      因為 tweened 也是一種 Store,所以也可以用 $progress 來取得 progress 當中儲存的變數的值,並且用 set 這個方法來改變儲存的變數。

  • 第十二行:<progress value={$progress} />
      在 HTML 當中也可以展開 Javascript,並且用 $progress 拿到變數的值。

https://i.ibb.co/zJ1gbpr/28.gif
圖二、看我伸縮自在的 tweened

  是不是可愛多了呢?tweened 可以在前值根後值之間做出補間的效果,藉此讓使用者互動介面的表現能力更強。既然 tweened 有補間的效果,想必我們也可以使用參數來改變補間的變化方式囉。沒錯,tweened 提供四個參數讓我們可以對補間效果做更自由的調整,這幾個參數包括:

  • delay:補間效果開始的時間,單位是毫秒。
  • duration:補間效果持續的時間,單位同樣是毫秒。
  • easing:補間效果的變化方式,就跟昨天第 27 天:Svelte 的過場:transition介紹過的 easing 是相同的意思。
  • interpolate:補間的定義方式。除了數字之外,藉由 interpolate,只要定義得當,我們也可以對文字、陣列、或是色碼做出補間的效果。

  看到了嗎?色碼也可以用 tweened 來做出補間的效果,是不是很棒呢。今天就讓我們用新學的動態效果 tweened 來替我們的色票產生器做出更出色的變化吧!

利用 tweened 實作動態漸變色票

  我們的色票產生器可以隨機產生不同色碼的色票,今天想要實作的效果,就是在色碼重新進行隨機生成的時候,加入色碼補間的效果,讓色票是逐漸地從原本的顏色變成新的顏色。用文字說明太抽象的話,先來實際來看個成果吧:

https://i.ibb.co/r01FVmx/28.gif
圖三、令人著迷的漸變色票🎉

  在開始動手前,讓我們先把 Palettes.svelte 重新整理一下:

/src/lib/Palettes.svelte
<script>
  import Palette from "./Palette.svelte";
  export let palettes;
</script>

<div class="palettes">
  {#each palettes as { hex, locked, id } (id)}
    <Palette bind:hex bind:locked />
  {:else}
    <div class="card no-color">
      <div>
        <p>No color to show. Please add a color.</p>
      </div>
    </div>
  {/each}
</div>
  • 第二行:import Palette from "./Palette.svelte";
      我們想要把 each 段落當中迭代的一張一張色票提取出來,變成新的 Svelte 元件 Palette.svelte。所以這邊先說我們要引入 Palette 這個 Svelte 元件,等等我們在來詳細把 Palette 實作出來。

  • 第八行:<Palette bind:hex bind:locked />
      引入 Palette 之後,就可以在 each 邏輯區塊當中簡簡單單的寫這麼一行,其他詳細的內容就可以放在 Palette 這個 Svelte 元件裡頭。記得要把 hexlocked 都用 bind 綁定起來。

  接著來實作 Palette.svelte

/src/lib/Palette.svelte
<script>
  import unlock from "../assets/unlock.svg";
  import lock from "../assets/lock.svg";
  import { toastStore } from "./toastStore";
  export let hex;
  export let locked;

  const getHexName = async (hex) => {
    const url = `https://www.thecolorapi.com/id?hex=${hex}`;
    const res = await fetch(url);
    const json = await res.json();
    return json.name.value;
  };

  const handleLock = ({ locked, hex }) =>
    toastStore.addToast({ action: locked ? "lock" : "unlock", hex });
</script>

<div class="card">
  <div class="palette" style="background: #{hex}" />
  <div class="hex-code">
    <input type="text" size="10" bind:value={hex} />
    <p>
      {#await getHexName(hex)}
        wait...
      {:then name}
        {name}
      {/await}
    </p>
  </div>
  <!-- svelte-ignore a11y-no-static-element-interactions a11y-click-events-have-key-events -->
  <div class="lock-icon">
    <label>
      <input
        type="checkbox"
        bind:checked={locked}
        on:change={() => handleLock({ locked, hex })}
      />
      {#if locked}
        <img src={lock} alt="color-locked" />
      {:else}
        <img src={unlock} alt="color-unlocked" />
      {/if}
    </label>
  </div>
</div>
  • 第五行:export let hex;
      記得要將 hexexport 這個關鍵字使其變成 Palette 這個 Svelte 元件的 Property。

  • 第六行:export let locked;
      記得要將 lockedexport 這個關鍵字使其變成 Palette 這個 Svelte 元件的 Property。

  剩下的程式碼沒有太大改變就不贅述了。如此一來就完成將 Palettes.svelte 當中代表一張一張色票的程式碼獨立出來的動作了。接下來我們就來客製化一下 tweened,做出可以對色碼進行補間的 tweened。首先建立一個新的檔案 hexStore.js

/src/lib/hexStore.js
import { tweened } from "svelte/motion";

export const createHexStore = (hex) => {
  const hexStore = tweened(hex, {
    duration: 500,
    interpolate: (a, b) => (t) => {
      const rA = parseInt(a.slice(0, 2), 16);
      const gA = parseInt(a.slice(2, 4), 16);
      const bA = parseInt(a.slice(4, 6), 16);

      const rB = parseInt(b.slice(0, 2), 16);
      const gB = parseInt(b.slice(2, 4), 16);
      const bB = parseInt(b.slice(4, 6), 16);

      const rT = ("0" + Math.round((rB - rA) * t + rA).toString(16)).slice(-2);
      const gT = ("0" + Math.round((gB - gA) * t + gA).toString(16)).slice(-2);
      const bT = ("0" + Math.round((bB - bA) * t + bA).toString(16)).slice(-2);

      return rT + gT + bT;
    },
  });

  return hexStore;
}
  • 第一行:import { tweened } from "svelte/motion";
      引入 tweened

  • 第三行:export const createHexStore = (hex) => {
      宣告一個函式 createHexStore。這個函式會吃進一個參數 hex,並回傳可以對色碼進行補間的 tweened

  • 第四行:const hexStore = tweened(hex, {
      初始化一個 tweened

  • 第五行:duration: 500,
      設定 tweened 補間的變化全程時間為 500 毫秒。

  • 第六行:interpolate: (a, b) => (t) => {
      這就是客製化 tweenedtweened 能夠對色碼進行補間的重點。interpolate 是一個函式,接受代表起始值 a 跟結束值 b 的參數,然後回傳一個補間函式,也就是時間 t 的時候應該呈現的補間的值。舉例來說,如果起始值是 1,結束值是 0,補間為線性變化,那麼時間進行了 0.5 時,補間函式應該要能算出補間的值為 0.5。用色碼來做解釋的話,如果起始色碼是 rgb(0, 0, 0),結束色碼是 rgb(255, 255, 255),同樣讓補間線性變化,那麼時間是 0.5 時,補間函式應該要能算出補間色碼為 rgb(127.5, 127.5, 127.5)。不過色碼不允許小數點,所以還要做個 Math.round() 四捨五入一下。

  這樣一來就完成我們客製化的 tweened 了。回頭看看前面 tweened 應用的例子,其實我們想要做的動作就是用 tweened 儲存色碼的資料,並顯示在 <div class="palette"> 這個 HTML 元素上。現在已經有了可以對色碼做出補間的 tweened 了,讓我們把這個厲害的 tweened 放進代表一張色碼的 Palette.svelte 當中:

/src/lib/Palette.svelte
<script>
  /* 省略無關的程式碼 */
  import { createHexStore } from "./hexStore";

  const hexStore = createHexStore(hex);
  $: hexStore.set(hex);
</script>

<div class="card">
  <div class="palette" style="background: #{$hexStore}" />
  <div class="hex-code">
    <!—省略無關的程式碼 -->
  </div>
  <div class="lock-icon">
    <!—省略無關的程式碼 -->
  </div>
</div>
  • 第三行:import { createHexStore } from "./hexStore";
      引入 createHexStore

  • 第五行:const hexStore = createHexStore(hex);
      我們用 createHexStore(hex) 初始化一個客製化的 tweened

  • 第六行:$: hexStore.set(hex);
      用第 09 天:Svelte 中的 Javascript:陳述介紹過的互動陳述 $:,如果 hex 發生變化,那就一起更新我們的 hexStore。記得 hexStore 本質上就是 tweened,就是一種 Store,所以可以用 hexStore.set 來改變儲存在裡面的變數。

  • 第十行:<div class="palette" style="background: #{$hexStore}" />
      並且在 <div class="palette"> 當中將 background 設定成 $hexStore,利用自動訂閱 (auto-subscription) 的方式,直接取得 hexStore 當中的值。

https://i.ibb.co/r01FVmx/28.gif
圖四、再依次看著令人著迷的漸變色票🎉

  今天關於 Svelte 動態效果 tweened 的介紹就到這邊了。完整的程式碼可以在 Github 資料庫當中找到,那麼謝謝大家的閱讀!


上一篇
第 27 天:Svelte 的過場:`transition`
下一篇
第 29 天:Svelte 在 HTML 元素上的修飾
系列文
了不起的 Svelte30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言