⛷️

TS/CSSで行をスライドさせる削除機能

2024/11/28に公開

はじめに

Reactで作ったWebアプリの日時別データ登録機能で、行削除を実装した時の記録です。

最終的な見た目

こんな感じの行に対し、

右からにゅっと削除ボタンを出すようにしました。

最初の実装と問題点

最初はiPhone標準機能にあるような、左に向かってスライドすると削除ボタンが現れる、という仕様にしようとしていました。

以下のように overflow:hidden した親コンポーネントの中で、データごと左にずれる感じです。
スクロールし終わった位置をもとに、左端か右端に自動で吸い寄せられるようにしました。
(動画がなくてわかりづらくすみません。iPhone標準でよくある感じです。)

実装は、TSで以下のように行の左端と右端を scrollIntoView で表示させるようにしました。

page.tsx
// スクロールする行の親要素
const componentRef = useRef<HTMLDivElement>(null);
// スクロールする行
const contentRef = useRef<HTMLDivElement>(null);
// 行左側のデータ部分
const dataRef = useRef<HTMLDivElement>(null);
// 行右側の削除ボタン部分
const deleteRef = useRef<HTMLDivElement>(null);

const handleScroll = () => {
    // 横スクロールし終わった時点の左端の位置を取得
    const left = contentRef.current?.getBoundingClientRect().left;

    // 一定の距離までスクロールされたら
    if (left !== undefined && left < 14) {
      // アニメーション付きで、行の右端まで自動スクロール(削除ボタン表示)
      deleteRef.current?.scrollIntoView({ behavior: "smooth", inline: "end" });
    // スクロール距離が足りなかったら
    } else {
      // アニメーション付きで、行の左端まで自動スクロールで戻す(削除ボタン非表示)
      dataRef.current?.scrollIntoView({ behavior: "smooth", inline: "start" });
    }
  };

  // スクロールが終了したことを検知
  useEffect(() => {
    const element = componentRef.current;
    if (element) {
      element.addEventListener("scrollend", handleScroll);
      return () => {
        element.removeEventListener("scrollend", handleScroll);
      };
    }
  }, []);

PCとAndroidのChromeではうまくいきましたが、iPhoneのChromeでは自動スクロールが動いてくれず、スクロールした位置でそのまま止まってしまいました。

iPhoneでも動くように修正してみようかと思いましたが、そもそもiPhoneユーザー以外がスライドで削除ボタンを表示することに慣れていないので、この機能自体違和感を持ってしまうのではと思い、他の仕様に変更することに。

最終的な実装

結局、表の上に配置した「編集」というボタンを押したら、すべての行で一斉に削除ボタンが表示される、という仕様にしました。

一斉に表示された時のイメージ図↓

削除ボタン表示のアニメーションは、CSSで以下のように実装しました。

page.module.css
// はみ出た削除ボタン部分を隠している親要素
.component {
  width: 100%;
  height: 42px;
  position: relative;
  overflow: hidden;
  border-radius: 5px;
}
// 削除ボタンを含む子要素
// widthで、100%はデータ行部分、70pxは削除ボタン部分
// transitionで、widthが変化した時にアニメーションさせている
.content {
  display: flex;
  flex-direction: row;
  font-weight: 700;
  background-color: white;
  width: calc(100% + 70px);
  height: 100%;
  position: absolute;
  transition: width 0.3s;
}
// 編集ボタン押下時、widthを削除ボタン含めて親要素の範囲内におさめる
.showDelete {
  width: 100%;
}

おわりに

結局、iPhoneでだけ scrollIntoView が効かなかった原因がわからずじまいです。
scrollIntoView が効いていないのか、他に問題があったのか・・・。

Discussion