速報

X.Orgのクライアントライブラリーで17件を修正!Linux端末と画像処理の利用範囲を点検

CATEGORY

【GSAP】Draggableで動かせるUIを作る|boundsとキーボード操作の実装

※本ページのリンクにはプロモーションが含まれています。

濃い背景に配置したGSAP公式ブランドロゴ

目次

情報確認日:2026年10月15日。GSAP 3系の公式資料を確認して作成した、本サイト独自の実装例です。

ドラッグできる要素を領域内へ収め、矢印キーでも移動できるデモを作ります。bounds、位置の同期、タッチ操作、読み上げへの通知を説明します。

スポンサーリンク

ドラッグできることと、操作しやすいことを分ける

Draggableは、要素の移動や回転などをポインター操作へ結び付けるプラグインです。少ない設定でマウスやタッチによる移動を追加できます。ただし、ドラッグだけを唯一の操作方法にすると、その操作が難しい利用者を置き去りにする可能性があります。

今回のデモは、部品を領域内で動かすだけの小さな例です。ファイルのアップロード、一覧の並べ替え、地図の業務操作を完成させるものではありません。矢印キーとHomeキーを追加し、ドラッグしなくても位置を変えられる経路を用意します。

typeとboundsを最初に決める

type: "x,y"は、要素を横と縦へ移動する設定です。横方向だけなら"x"、回転なら"rotation"など、操作の目的に合わせて選びます。自由移動の後で不要な軸を毎フレーム戻すより、最初から制約として表す方が理解しやすくなります。

boundsへarena要素を渡して、移動できる領域を指定します。親がposition: relative、部品がposition: absoluteという単純な配置にして、初期状態を左上へ揃えています。複雑なtransformを親子へ重ねる前に、この構成で移動範囲を確認してください。

スポンサーリンク

boundsはデータの妥当性検証ではない

領域内に動きを収めても、サーバーへ保存する座標の正しさまでは保証しません。ユーザーが送る値は、ドラッグ以外の方法でも変えられます。予約枠や権限を伴う配置情報を保存する場合は、サーバー側で範囲、権限、対象IDを検証する必要があります。

このデモの位置は画面内の一時的な値です。ブラウザーを再読み込みすれば初期位置へ戻ります。保存する要件があるなら、CSSピクセルをそのまま保存するのか、領域に対する割合へ変換するのかも決めます。画面幅が変わるサイトでは、その選択が復元結果へ影響します。

外部から位置を変えたらDraggableへ同期する

キーボード処理はgsap.setでtransformを変更します。その後にdrag.update()を呼び、Draggableが認識する位置と、実際の要素の状態を同期します。同期せずにドラッグを再開すると、古い位置を前提に扱っていないかを確認する必要があります。

矢印キーは20pxずつ移動し、minX、maxX、minY、maxYの範囲にclampしています。Homeは今回の初期配置である0,0へ戻します。異なる初期配置へ移すなら、Homeの移動先もその要件へ合わせて見直してください。

スポンサーリンク

操作の説明とフォーカスの見た目を付ける

divは標準ではキーボードフォーカスを受け取らないため、tabindexを付けています。aria-describedbyで操作説明と結び付け、focus-visibleで現在の対象を分かるようにしています。見た目だけ緑色の四角を置き、ドラッグできることを推測させる構成にはしません。

これは独自操作のデモであり、標準のrange入力などで要件を満たせる場合は、その標準部品を先に検討します。特に値の正確な入力が目的なら、ドラッグに加えて数値入力や明示的なボタンを用意する方が適していることがあります。

通知は毎フレーム読み上げない

位置を示すrole="status"は、ドラッグ終了時とキーボード操作時に更新します。onDragで細かい座標を読み上げ続けると、重要な情報が大量の通知へ埋もれる可能性があります。目で見えるリアルタイムの座標が必要な場合も、読み上げ用と同じ頻度で更新する必要があるか考えます。

サンプルは部品を直接動かすため、移動を滑らかに追いかけるTweenや慣性は追加していません。inertia: trueを使う場合はInertiaPluginの読み込みと登録が別に必要です。Draggableだけで自動的に慣性が付くわけではありません。

タッチの制御をページ全体へ広げない

touch-action: noneは動かす部品に限定しています。ページ全体のタッチスクロールを無効にすると、利用者が別の情報へ移動しにくくなります。小さな部品だから問題がないと決めず、実機で部品を避けて縦スクロールできるか確認してください。

ウィンドウの幅が変わったときはapplyBoundsで領域を適用し直しています。親のサイズがウィンドウ以外の理由で変わる場合は、その変更完了時にも再適用が必要か検討します。SPAではdrag.kill()に加え、keydownやresizeのイベントも取り除く処理を用意してください。

公開前はポインターを使わずに試す

Tabで部品へ移動し、矢印キーで四方向へ動かし、端で止まり、Homeで戻れることを確認します。その後にマウスと実機タッチを試します。ポインター操作が快適でも、キーボードで到達できなければ、同じ機能を利用できるとは言えません。

装飾として動かすのか、業務上の値を入力するのかで必要な設計は変わります。Draggableを入れた段階を完成とせず、代替操作、結果の説明、保存の検証まで含めて必要な範囲を決めましょう。

スポンサーリンク

実装例:HTML・CSS・JavaScript

以下は独立したデモ用のコードです。既存のViteなど、npmパッケージを解決できる開発環境で npm install gsap を実行し、HTMLをbody内、CSSをスタイルシート、JavaScriptをmain.jsへ配置してください。main.jsはHTMLの末尾からtype="module"で読み込みます。WordPressの記事本文へそのまま実行用スクリプトを貼る例ではありません。

<p id="help">緑の部品をドラッグできます。フォーカス後は矢印キーで移動、Homeキーで左上へ戻します。</p>
<div class="arena">
  <div class="handle" tabindex="0" role="group" aria-label="移動できる部品" aria-describedby="help">移動</div>
</div>
<p id="position" role="status">位置は左上です。</p>
.arena { position:relative; width:min(90vw,36rem); height:20rem;
  margin:2rem auto; border:2px solid #42433d; background:#fffce1; }
.handle { position:absolute; top:0; left:0; width:5rem; height:5rem;
  display:grid; place-items:center; background:#0ae448; border-radius:1rem;
  cursor:grab; touch-action:none; user-select:none; }
.handle:focus-visible { outline:3px solid #5125bb; outline-offset:3px; }
.handle:active { cursor:grabbing; }
import { gsap } from "gsap";
import { Draggable } from "gsap/Draggable";
gsap.registerPlugin(Draggable);
const arena = document.querySelector(".arena");
const handle = document.querySelector(".handle");
const status = document.querySelector("#position");
function report() {
  status.textContent = `位置:横${Math.round(drag.x)}、縦${Math.round(drag.y)}ピクセル`;
}
const [drag] = Draggable.create(handle, {
  type: "x,y", bounds: arena, edgeResistance: 1,
  onDragEnd: report
});
handle.addEventListener("keydown", (event) => {
  const moves = { ArrowLeft: [-20, 0], ArrowRight: [20, 0],
    ArrowUp: [0, -20], ArrowDown: [0, 20] };
  if (!(event.key in moves) && event.key !== "Home") return;
  event.preventDefault();
  let x = 0, y = 0;
  if (event.key !== "Home") {
    x = gsap.utils.clamp(drag.minX, drag.maxX, drag.x + moves[event.key][0]);
    y = gsap.utils.clamp(drag.minY, drag.maxY, drag.y + moves[event.key][1]);
  }
  gsap.set(handle, { x, y });
  drag.update();
  report();
});
window.addEventListener("resize", () => { drag.applyBounds(arena); report(); });

サンプルを確認するときの条件

APIの使い方は公式資料と照合しています。掲載コードはGSAP 3.15.0とDOMを模した環境で読み込み・操作を確認しましたが、実ブラウザーでの見た目、タッチ操作、描画性能を保証するものではありません。採用前に対象ブラウザーと実機で確認してください。JavaScriptを無効にした状態でも、必要な内容と操作が残るかを確認します。

スポンサーリンク

参考資料

この記事を書いた人

Ryohei

Webエンジニア / アプリ開発者 / 技術ブログ運営者

福岡のWeb制作会社に勤務するWebエンジニアです。10年以上にわたりWeb制作・開発に携わり、これまでに100以上のWordPressサイトの構築・保守を担当してきました。Webサイト制作に加え、アプリ開発やAIを活用した開発にも取り組み、近年はWebセキュリティにも注力しています。

2015年から技術メディア「Into the Program」を運営し、実務や開発で得た知識をもとに、プログラミングの実装方法やWebサイトの運用・セキュリティ情報を発信しています。

主な分野
WordPress / PHP / JavaScript / アプリ開発 / Webサイトの運用・セキュリティ対策
資格・試験合格
情報セキュリティマネジメント試験
基本情報技術者試験
AWS認定ソリューションアーキテクト アソシエイト
メディア運営
2015年〜

記事の作成・更新について

セキュリティ記事は、ベンダーの公式アドバイザリやCVE・JVNなどの一次情報を参照し、出典と情報確認日を記載します。実環境での検証を行った場合は、その環境と確認範囲を明示します。重要な追加情報や誤りを把握した場合は、内容を確認して追記・訂正します。

スポンサーリンク

関連する記事

おすすめの書籍