DEMO
CODE
<div class="demo-stage swd-stage">
<div class="swd-box">
<ul class="swd-list" id="swd-list" aria-label="通知"></ul>
<button type="button" class="swd-reset" id="swd-reset" hidden>通知を元に戻す</button>
</div>
</div>
.swd-stage { display:flex; align-items:center; justify-content:center; padding:1.5rem; background:#0B0B10; min-height:300px; overflow:hidden; }
.swd-box { width:100%; max-width:340px; display:flex; flex-direction:column; gap:.6rem; align-items:center; }
.swd-list { list-style:none; margin:0; padding:0; width:100%; display:flex; flex-direction:column; gap:.55rem; }
.swd-card { position:relative; display:flex; align-items:center; gap:.8rem; padding:.8rem .9rem; border-radius:12px; background:#17171C; border:1px solid rgba(255,255,255,.1);
touch-action:pan-y; user-select:none; cursor:grab; transition: transform .3s cubic-bezier(.22,1,.36,1), opacity .3s ease; }
.swd-card.is-drag { transition:none; cursor:grabbing; }
.swd-card:focus-visible { outline:2px solid #E91B89; outline-offset:2px; }
.swd-ic { flex:0 0 34px; height:34px; border-radius:9px; display:flex; align-items:center; justify-content:center; background:rgba(233,27,137,.16); }
.swd-body { flex:1; min-width:0; }
.swd-t { display:block; color:#EDEDED; font-size:.84rem; }
.swd-s { display:block; color:#8A8A96; font-size:.72rem; }
.swd-x { flex:0 0 32px; height:32px; border:0; border-radius:8px; background:transparent; color:#8A8A96; font-size:1rem; cursor:pointer; }
.swd-x:hover { background:rgba(255,255,255,.08); color:#fff; }
.swd-x:focus-visible { outline:2px solid #E91B89; }
.swd-hint { color:#6E6E7A; font-size:.72rem; }
.swd-reset { min-height:40px; padding:0 1rem; border-radius:999px; border:1px solid rgba(255,255,255,.2); background:transparent; color:#C9C9D1; font-size:.8rem; cursor:pointer; }
.swd-reset:focus-visible { outline:2px solid #E91B89; outline-offset:2px; }
@media (prefers-reduced-motion: reduce) {
.swd-card { transition-duration:.01ms; }
}
(function(){
var list = document.getElementById('swd-list');
var reset = document.getElementById('swd-reset');
if (!list || !reset) return;
var DATA = [['📦', '荷物が発送されました', '2分前'], ['💬', '新しいコメントがあります', '15分前'], ['🔔', '予約の前日になりました', '1時間前']];
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
function remove(card, dir){
var li = card.parentNode;
card.style.transform = 'translateX(' + (dir * 120) + '%)'; card.style.opacity = '0';
setTimeout(function(){
var sib = li.nextElementSibling || li.previousElementSibling;
li.remove();
var target = sib && sib.querySelector('.swd-card');
if (target) target.focus(); else { reset.hidden = false; reset.focus(); }
}, reduce.matches ? 0 : 300);
}
function build(){
list.innerHTML = ''; reset.hidden = true;
DATA.forEach(function(d){
var li = document.createElement('li');
var c = document.createElement('div');
c.className = 'swd-card'; c.tabIndex = 0;
c.setAttribute('aria-label', d[1] + '(' + d[2] + ')。Delete キーで消去');
c.innerHTML = '<span class="swd-ic" aria-hidden="true">' + d[0] + '</span><span class="swd-body"><span class="swd-t">' + d[1] + '</span><span class="swd-s">' + d[2] + '</span></span>';
var x = document.createElement('button');
x.type = 'button'; x.className = 'swd-x'; x.textContent = '×'; x.setAttribute('aria-label', d[1] + 'を閉じる');
x.addEventListener('click', function(e){ e.stopPropagation(); remove(c, 1); });
x.addEventListener('pointerdown', function(e){ e.stopPropagation(); });
c.appendChild(x);
var startX = 0, dx = 0, id = null;
c.addEventListener('pointerdown', function(e){ id = e.pointerId; startX = e.clientX; dx = 0; c.setPointerCapture(id); c.classList.add('is-drag'); });
c.addEventListener('pointermove', function(e){
if (e.pointerId !== id) return;
dx = e.clientX - startX;
c.style.transform = 'translateX(' + dx + 'px)';
c.style.opacity = String(1 - Math.min(Math.abs(dx) / c.offsetWidth, 1) * .7);
});
function end(e){
if (e.pointerId !== id) return;
id = null; c.classList.remove('is-drag');
if (Math.abs(dx) > c.offsetWidth * .35) remove(c, dx > 0 ? 1 : -1);
else { c.style.transform = ''; c.style.opacity = ''; }
}
c.addEventListener('pointerup', end);
c.addEventListener('pointercancel', end);
c.addEventListener('keydown', function(e){ if (e.key === 'Delete' || e.key === 'Backspace') { e.preventDefault(); remove(c, 1); } });
li.appendChild(c); list.appendChild(li);
});
}
reset.addEventListener('click', function(){ build(); var f = list.querySelector('.swd-card'); if (f) f.focus(); });
build();
})();
AI PROMPT
指やマウスで横にスワイプすると消える通知カード(Swipe to Dismiss)を作って。Pointer Events(pointerdown / pointermove / pointerup / pointercancel)でマウスとタッチをまとめて扱い、pointerdown で setPointerCapture を呼んでカードの外へ指が出ても追跡を続ける。ドラッグ中は transition を切って translateX(移動量) で指に追従させ、移動量に応じて不透明度を下げる。指を離した時、移動量がカード幅の35%を超えていれば、その方向へ translateX(±120%)・透明 にして .3秒で飛ばし、終わったら要素を取り除く。超えていなければ transform を空に戻して元の位置へ戻す(cubic-bezier(.22,1,.36,1))。縦スクロールを妨げないようカードに touch-action: pan-y を付ける。スワイプできない利用者のために、各カードに aria-label 付きの「×」ボタンを置き、カード自体もフォーカスできるようにして Delete キーでも消せるようにする。消したら次のカードへフォーカスを移し、全部消えたら「通知を元に戻す」ボタンを出す。@media (prefers-reduced-motion: reduce) では飛んでいく動きを省略する。 Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
通知やメールの一覧で、項目を横にスワイプするとその項目が画面の外へ飛んで消える操作です。スマホのOSやメールアプリで広く使われており、片手の親指だけで不要な通知を次々に片づけられます。
当サイトの Framer Drag は Framer Motion で要素をドラッグする演出、Toast Notification は一時的な通知の表示、Swipe Carousel はスワイプで画像を送るカルーセルです。本ページはスワイプで項目を消す操作と、消すかどうかのしきい値の判定を扱う別テーマです。
スワイプは便利な反面、使えない人もいる操作です。同じことができるボタンとキー操作を必ず並べ、消したあとのフォーカスの行き先まで決めておくと、誰でも使える一覧になります。誤って消した時のために、元に戻す手段を用意しておくことも大切です。
touch-action: pan-y は何のためですか?
スマホでカードに触れたとき、ブラウザは標準で「スクロールか拡大か」を判断して処理します。touch-action: pan-y を指定すると、縦方向のスクロールはブラウザに任せたまま、横方向の動きだけをページ側で受け取れます。これで縦にスクロールしたいのにカードが横へ動いてしまう、という誤操作を防げます。
スワイプだけで消せる作りにしてはいけないのですか?
スワイプは、マウスやキーボードで操作する人、指先の細かい動きが難しい人には使えないことがあります。WCAG 2.2 でも、ドラッグ操作には1回のクリックやタップで代替できる手段を用意することが求められています。本実装は「×」ボタンと Delete キーでも同じ操作ができるようにしています。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。