デモ
デモを読み込めませんでした。
仕様
- 用途
- カード
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- ScrollTrigger
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
カードを 48px 下げて透明にしておき、上端が画面の下から 15% の高さに届いたところで、元の位置へ戻しながら見せます。同じころに画面へ入ったカードはひとまとめに扱い、0.1 秒ずつずらして出すので、行ごとに左から順に現れます。
動く距離は 48px、時間は 0.8 秒、ease は power3.out です。距離を大きくすると、読み進める速さに動きが追いつかず、遅れて出てくるように見えます。ずらす間隔の 0.1 秒は、3 枚の行がおよそ 0.2 秒で出そろう長さで、これより広げると最後の 1 枚を待つ感じが出ます。
出し始める線(start)は、画面の下端ではなく少し内側に置きます。下端に置くと、カードが顔を出した瞬間に動き始めるので、いちばん動いている所が画面の外で終わってしまいます。
1 枚ずつ結ぶのではなく、まとめて扱うのが要点です。1 枚ずつだと、速くスクロールしたときに何枚もばらばらに動き出し、順に現れる感じが消えます。戻したときに消えてしまわないよう、出す向きだけを書いて、上へ戻したときは何もしません。
動きを減らす設定の人には、最初から出ている状態で見せます。記事の一覧・実績・商品の並びなど、同じ形の箱が続く節に向いています。
コード
<section class="cards" aria-labelledby="cards-title">
<h2 class="cards__title" id="cards-title">Journal</h2>
<div class="cards__grid">
<!-- 同じ形の箱を並べるだけ。枚数が増えても、つなぎ方は変わらない -->
<article class="card">
<a class="card__link" href="/journal/quiet-interfaces/">
<img class="card__image" src="/img/journal-01.jpg" alt="" width="800" height="500">
<h3 class="card__name">Quiet interfaces</h3>
<p class="card__text">目立たせるより、読みやすさを選んだ画面の話。</p>
</a>
</article>
<article class="card">
<a class="card__link" href="/journal/slow-motion/">
<img class="card__image" src="/img/journal-02.jpg" alt="" width="800" height="500">
<h3 class="card__name">Slow motion</h3>
<p class="card__text">速さではなく、間の取り方で伝わる動きについて。</p>
</a>
</article>
<article class="card">
<a class="card__link" href="/journal/paper-and-ink/">
<img class="card__image" src="/img/journal-03.jpg" alt="" width="800" height="500">
<h3 class="card__name">Paper and ink</h3>
<p class="card__text">紙の組み方を、画面の余白へ持ち込んだ記録です。</p>
</a>
</article>
</div>
</section>
.cards {
--ink: #262626;
--mute: #6b6b6b;
padding: 64px 6vw;
background-color: #f2f1ed;
color: var(--ink);
}
.cards__title {
margin: 0 0 32px;
font-size: clamp(24px, 2.6vw, 32px);
font-weight: 800;
line-height: 1.2;
letter-spacing: -0.02em;
}
.cards__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 32px;
}
/* カードは白い面。角は 4px で、影は付けない */
.card {
border-radius: 4px;
background-color: #ffffff;
}
.card__link {
display: block;
padding: 16px;
color: inherit;
text-decoration: none;
}
.card__link:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 2px;
}
.card__image {
display: block;
width: 100%;
height: auto;
aspect-ratio: 16 / 10;
object-fit: cover;
}
.card__name {
margin: 16px 0 0;
font-size: 18px;
font-weight: 700;
line-height: 1.4;
letter-spacing: 0.02em;
}
.card__text {
margin: 8px 0 0;
font-size: 14px;
line-height: 1.8;
letter-spacing: 0.04em;
color: var(--mute);
}
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const cards = gsap.utils.toArray(".card");
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
// 動きを減らす設定では何もしない(カードは最初から出ている状態のまま)
if (cards.length > 0 && !reduceMotion) {
gsap.set(cards, { y: 48, opacity: 0 });
// 同じころに画面へ入ったカードをまとめて扱う。1 枚ずつ結ぶと、速く送ったときにばらばらに動き出す
ScrollTrigger.batch(cards, {
// 画面の下端ではなく、少し内側から出し始める。下端だと、いちばん動く所が画面の外で終わる
start: "top 85%",
onEnter: (batch) =>
gsap.to(batch, { y: 0, opacity: 1, duration: 0.8, ease: "power3.out", stagger: 0.1, overwrite: true }),
});
}