Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Web workerを使ってUXを向上させようとした話

kurisaki kazuma
September 26, 2023
280

Web workerを使ってUXを向上させようとした話

We Are JavaScripters! @42nd で発表した資料です。

WeJs
https://wajs.connpass.com/event/293440/

kurisaki kazuma

September 26, 2023
Tweet

Transcript

  1. Web worker を使って UX を向上させようとした話
    1

    View full-size slide

  2. 自己紹介
    栗崎一真
    フロントエンドエンジニア
    2021 卒 楽天グループ株式会社 AI SHIFT (サイバーエージェント)
    X: @KK_sep_TT
    GitHub: @kult0922
    趣味
    個人開発
    旅行
    ポーカー 2

    View full-size slide

  3. はじめに
    写真からマインクラフトのドット絵に変換するサービスを開発しています
    https://www.minecraft-dot.pictures 3

    View full-size slide

  4. UX 改善したいところ
    変換中に画面が固まらないようにしたい
    JS で画像処理を行っているので画面をロックしてしまう
    変換中はローディングのアニメーションを出したい
    あったら嬉しい機能
    変換の進捗を表示したい
    4

    View full-size slide

  5. なぜ画面がロックされてしまうのか
    JS は画面の処理をメインスレッドで行う
    メインスレッドで重い処理を行うと処理中は画面処理にリソースを使えない
    5

    View full-size slide

  6. そこで Web worker を使う
    Web worker とは
    メインスレッドとは独立して JS をバックグラウンドで実行できる Web API
    メインスレッドのパフォーマンスに影響を与えず、時間のかかる処理ができる。
    メインスレッドとは別なので UI をロックしない
    6

    View full-size slide

  7. Worker の使い方
    worker を呼び出す側
    worker = new Worker("worker.js");
    // worker
    から結果を受け取る
    worker.onmessage = function (event) {
    console.log("Received: ", event.data);
    };
    worker.postMessage(10); // worker
    に 10
    を引数で渡して実行
    worker.js
    self.addEventListener("message", (e) => {
    const result = e.data * e.data;
    postMessage(result);
    });
    7

    View full-size slide

  8. どこを Worker に切り出すか
    時間の掛かっている処理を worker 内で行う
    設計図からドット画像を構築するろころで時間がかかっていた
    8

    View full-size slide

  9. 作成した Worker
    const constructImageFromBlueprint = (blueprint: Array>) => {
    //
    設計図からドット画像を構築する処理
    ...
    return result;
    };
    self.addEventListener("message", (e) => {
    const result = constructImageFromBlueprint(e.data.blueprint);
    self.postMessage(result);
    });
    9

    View full-size slide

  10. カスタムフックで worker の状態を管理
    export function useWorker() {
    const [loading, setLoading] = useState(true);
    const run = (
    ) => {
    setLoading(true);
    const worker = new Worker(
    new URL("./worker", import.meta.url)
    );
    worker.onmessage = function (e) {
    // worker
    処理完了
    setLoading(false);
    ...
    };
    worker.postMessage({ blueprint, blockImageDataDict });
    };
    return { loading, run };
    } 10

    View full-size slide

  11. ここまで
    画面のロックを防ぎたい
    ローディングを表示したい
    進捗を知りたい
    11

    View full-size slide

  12. worker 側から進捗を伝える
    worker とメインスレッドの共有方法は基本的には message だけ
    進捗も結果と同様 message として送る
    type プロパティで進捗地なのか結果なのかを判定
    const constructImageFromBlueprint = (blueprint: Array>) => {
    for (let i = 0; i < rows; i++) {
    if (i % step === 0) {
    progress += stepNum;
    self.postMessage({
    type: "loading",
    payload: progress,
    });
    }
    ...
    }
    self.postMessage({
    type: "complete",
    payload: result,
    });
    };
    12

    View full-size slide

  13. カスタムフックも進捗を受け取るように修正
    export function useImageGeneratorWorker() {
    const [progress, setProgress] = useState(0); //
    進捗
    const [loading, setLoading] = useState(true);
    const run = (blueprint: string[][]) => {
    setLoading(true);
    const worker = new Worker(
    new URL("../worker/imageGeneratorWorker", import.meta.url)
    );
    worker.onmessage = function (e) {
    if (e.data.type == "loading") {
    setProgress(e.data.payload); // <-
    進捗を受け取る
    return;
    }
    setLoading(false);
    };
    worker.postMessage({ blueprint, blockImageDataDict });
    };
    return { progress, loading, run }; // <-
    進捗も返す
    } 13

    View full-size slide

  14. 結果
    https://www.minecraft-dot.pictures
    14

    View full-size slide

  15. requestAnimationFrame との比較
    同じようなことは requestAnimationFrame をつかってもできる。
    worker
    マルチスレッドなので環境によってはパフォーマンスがいい
    worker ファイルを作成して message でメインスレッドとやり取り
    message のやり取りはコピーなので大きいデータのやり取りでパフォーマンスが落ちる
    可能性がある
    reauestnimationFrame
    シングルスレッドですべての処理を行う
    重い処理を関数に分割して再帰的に実行 15

    View full-size slide

  16. まとめ
    Web worker を使用することでメインスレッドをロックせずに重い処理を実行
    進捗も message で送ることでプログレスバーを実装
    16

    View full-size slide