💀

より良いユーザー体験を求めて "Skeleton UI" について深掘りする

2024/07/05に公開

個人開発でTokeruという個人タスク管理ツールを作っている者です。
このアプリは「独り言を呟きながらタスクを管理する」をテーマにしています。

今回はそのTokeruでSkeleton UIを実装しました。
この記事ではSkeletonで実装する理由から、リソースの少ない個人開発でも簡単に取り入れていく方法について考えていきます。

Skeleton UIとは

Skeleton UIとは読み込みUIの一種で、コンテンツの枠組みだけを先に表示させてあげる方法です。
今回紹介する例は、SlackなどにもあるようなURLからOGPを表示する機能です。
Tokeruで実装した機能のSkeleton UIと読み込み後のコンテンツのUIです。

Skeletonを使う理由は、レイアウトシフトをなくすため

読み込み中のUIとしては他にはCircularProgressIndicatorを使うことも多いのではないでしょうか。
SkeletonUIを実装するとなるとCircularProgressIndicatorと比べるとまあまあ面倒なのですが、そのメリットについて考えてみます。

https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html

SkeletonUIを使うメリットは2つあると考えています。

  1. 読み込みの体感時間が短くなる
  2. レイアウトシフトがなくなる

個人的には、2つ目の「レイアウトシフトがなくなる」のメリットがあると思って導入しています。

理由1. 読み込みの体感時間が短くなる

SkeletonUIを使うと、ユーザーはコンテンツの読み込み時間を多少、短く感じるそうです。
個人的な感覚でも短く感じるというか、安心して待てる感覚はあります。

例えばCircularProgressIndicatorで待っているよりもSkeletonUIで待っている方が、「どんなコンテンツを準備してくれているか」を予想しやすく、予想しやすいものの方がユーザーはストレスに感じにくいはずです。
例えばこのUIでは、タイトル、説明、画像が表示されることが予想でき、表示される位置まで知ることができます。


タイトル、説明、画像が表示されることが予想できる

理由2. レイアウトシフトがなくなる

SkeletonUIをちゃんと実装できれば、レイアウトシフトを避けることができます。ちゃんと実装できていない例は後述します。

レイアウトシフトとは、UIの要素が意図せずいきなり動いてしまう現象のことです。レイアウトシフトが起こると、一般的に言われている問題としては誤ってクリックしてしまったり、カクツクため視覚的にも優しくありません。

こちらのGIFは今回開発した機能であえてSkeletonUIを使わず、何も表示しない(Flutter的にはSizedBox.shirnk()を使う)場合で実装した例です。
コンテンツが読み込まれたタイミングでコンテンツの周りの要素の位置も動いてしまいます。


SkeletonUIを使わない例

SkeletonUIを使った例も載せておきます


SkeletonUIを使った例

逆にSkeletonUIとコンテンツのUIが大きくかけ離れている場合、結局レイアウトシフトが起こってしまいます。
サービスによってはどんなコンテンツが読み込まれるかは、読み込まれてからしかわからない場合もあると思います。その場合、あえてSkeletonUIを使わない選択もあると思いますが、LINEの記事が参考になりそうなので一応置いておきます。(詳細は割愛)

https://logmi.jp/tech/articles/325589


ここからはSkeletonUIを実装する際に気をつけていることやTips的なことを書いていきます。
コードはFlutterで紹介していますが、他のフレームワークでも応用できるはずです。

Flutterで実装する際に試していること

AnimatedSwitcherでSkeletonとコンテンツを切り替える

SkeletonUIからコンテンツに切り替わるときにパッと切り替わってしまうと、目の負担が大きいと個人的には思います。
よりベターにするにはフェードアウト・フェードインしてコンテンツを切り替えるのがよりベターではないでしょうか。
FlutterではAnimatedSwitcherを使って手軽に要素をフェードアウト・フェードインして切り替えることができます。
以下の動画ではわかりやすいようにフェードアウト・フェードインをあえてゆっくり行なっていますが、実際は150msほどが自然に感じれそうです。


ゆっくりフェードアウト・フェードインする例

return AnimatedSwitcher(
  duration: const Duration(milliseconds: 150),
  child: asyncValue.when(
    data: (ogp) {
      return SampleWidget();
    },
    loading: () {
      return const SampleWidget.loading();
    },
    error: (error, _) {
      return const SizedBox.shrink();
    },
  ),
);

汎用的なSkeleton Widgetを実装する

SkeletonUIはそこまでリッチな要素を用意するわけではなく、大体はテキストの代わりのSkeletonか画像の代わりのSkeletonかなと思います。
そのため汎用的なSkeletonのコンポーネントを用意しておくと後々導入が楽になりそうです。

汎用的なコンポーネントを用意するにあたって、先ほど「レイアウトシフトがなくなる」で述べた通り、コンテンツと異なる大きさになってしまっては良いとは言えません。そのため、Tokeruでは、特にテキストに関してはフォント自体のサイズ(fontSize)とテキストの高さ(height)を考慮したコンポーネントを作っています。

インターフェイスとしてTextStyleを渡せるようにすると、実際のコンテンツで使うスタイルと統一できて楽です。

SkeletonText(width: 100, style: TextStyle(fontSize: 11, height: 1.4)),

/// [Text]の箇所で使用するスケルトンUI。
class SkeletonText extends StatelessWidget {
  /// 幅。
  final double width;

  /// [Text]で使用しているスタイル。
  final TextStyle style;

  /// 行数。
  final int lineLength;

  /// コンストラクタ。
  const SkeletonText({
    super.key,
    required this.width,
    required this.style,
    this.lineLength = 1,
  });

  
  Widget build(BuildContext context) {
    // フォントサイズと行の高さからパディングを計算
    final padding =
        (style.fontSize! * (style.height ?? 1.4) - style.fontSize!) * 0.5;
    return Column(
      children: [
        for (int i = 0; i < lineLength; i++)
          Container(
            width: width,
            padding: EdgeInsets.symmetric(vertical: padding),
            child: Container(
              height: style.fontSize!,
              decoration: BoxDecoration(
                color: context.appColors.backgroundSkeleton,
                borderRadius: BorderRadius.circular(4),
              ),
            ),
          ),
      ],
    );
  }
}

さいごに

読み込み中の画面のデザインは、個人開発はもちろん、業務上の実装でも意外と後回しになったりすることが多いのではないかなと思います。
しかし、ほとんどのプロダクトで必ず考える必要があります。
一瞬しか表示されないUIですが、こだわることでより印象の良いアプリだと認知されるはずです。

TokeruもまだまだSkeletonしかり、こだわりきれてない箇所があります。
よりこだわってその知見をアウトプットしていきたいので良かったらフォローお願いします。

また、TokeruもApp storeで配布しているので、ぜひ触ってみてください。

https://apps.apple.com/jp/app/tokeru/id6479510993?mt=12

Skeleton UIをより深ぼるための参考になる記事

https://logmi.jp/tech/articles/325589

https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a

Discussion