デモ
デモを読み込めませんでした。
仕様
- 用途
- ローディング
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- CustomEase
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
ヘッダーの下の細い帯を、長さの変わる 2 本の帯が続けて流れていきます。進み具合がわからない読み込みを表す、よく知られた動きです。帯の左端と右端をそれぞれ動かして、伸びたり縮んだりしながら流れる形を作っています。
1 本目はゆっくり出て途中で伸び、縮みながら抜けていく曲線を使い、2 本目は素早く飛び出して減速する曲線を使います。2 本目は 1 本目より 1.15 秒遅れて追いかけるので、常にどちらか 1 本が流れている状態になります。
2 本を同じ曲線・同じタイミングにすると、帯が重なって 1 本に見えてしまうので、曲線と遅れを変えて重ならないようにします。動きを減らす設定では帯を流す代わりに、溝の明るさだけをゆっくり明滅させて、読み込み中だと伝えています。
帯の色は塗りのままでもグラデーションに替えても、位置がずれずに流れます。ページの切り替えやデータの取得中など、短い待ち時間をさりげなく知らせたい場面に向いています。
コード
<div class="bar-loader" role="status">
<div class="bar-loader__track">
<span class="bar-loader__fill bar-loader__fill--long"></span>
<span class="bar-loader__fill bar-loader__fill--short"></span>
</div>
<span class="bar-loader__text">読み込み中</span>
</div>
.bar-loader {
--fill: #98B1C4; /* 帯の色(スレート) */
--track: rgba(238, 236, 230, 0.14); /* 溝の色 */
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 50;
}
.bar-loader__track {
position: relative;
height: 4px;
overflow: hidden;
background: var(--track);
}
.bar-loader__fill {
position: absolute;
top: 0;
bottom: 0;
background: var(--fill);
}
.bar-loader__text {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
import { gsap } from "gsap";
import { CustomEase } from "gsap/CustomEase";
gsap.registerPlugin(CustomEase);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function createLoader(root) {
const track = root.querySelector(".bar-loader__track");
const [longBar, shortBar] = root.querySelectorAll(".bar-loader__fill");
let a, b;
if (reduceMotion) {
// 帯を流す代わりに、溝の明るさだけをゆっくり明滅させる
gsap.set([longBar, shortBar], { autoAlpha: 0 });
a = gsap.to(track, { opacity: 0.45, duration: 1, ease: "sine.inOut", yoyo: true, repeat: -1 });
} else {
// 1 本目はゆっくり出て途中で伸び縮みながら抜ける曲線、2 本目は素早く飛び出して減速する曲線
const long = CustomEase.create("barLong", "M0,0 C0.65,0.815 0.735,0.395 1,1");
const short = CustomEase.create("barShort", "M0,0 C0.165,0.84 0.44,1 1,1");
a = gsap.fromTo(longBar, { left: "-35%", right: "100%" }, { left: "100%", right: "-90%", duration: 1.26, ease: long, repeat: -1, repeatDelay: 0.84 });
b = gsap.fromTo(shortBar, { left: "-200%", right: "100%" }, { left: "107%", right: "-8%", duration: 1.26, ease: short, repeat: -1, repeatDelay: 0.84, delay: 1.15 });
}
return {
// 読み込みが終わったら呼ぶ
done() {
gsap.to(root, {
autoAlpha: 0,
duration: reduceMotion ? 0 : 0.3,
onComplete: () => {
a.kill();
b?.kill();
root.remove();
},
});
},
};
}
export const loader = createLoader(document.querySelector(".bar-loader"));