HTML / CSS
テキストの無限ループアニメーション
CSSのみ
・ループするテキストは複数用意すること
・短い単語の場合は、テキストを横並びにしたときに表示したいエリアの横幅(画面の横幅)を超えるように複数設置する必要があります
ABCDEFG HIJKLMN
ABCDEFG HIJKLMN
ABCDEFG HIJKLMN
HTML
ABCDEFG HIJKLMN
ABCDEFG HIJKLMN
ABCDEFG HIJKLMN
CSS
.loop-text.-css {
overflow: hidden; /* はみ出したテキストは表示しない */
display: flex;
width: 100vw;
margin-inline: calc(50% - 50vw);
}
.loop-text.-css .loop-text__item {
flex-shrink: 0; /* 要素を縮めたくない */
white-space: nowrap; /* 要素を改行させない */
font-size: 120px;
font-style: italic;
font-weight: bold;
color: #555555;
letter-spacing: 0.05em;
&:nth-child(odd) {
animation: MoveLeft 24s -12s infinite linear;
/* 24秒かけて-12秒後に無限ループさせる */
animation-fill-mode: both;
}
&:nth-child(even) {
animation: MoveLeft2 24s infinite linear;
/* 24秒かけて無限ループさせる */
animation-fill-mode: both;
}
}
・流れるアニメーションの動きはkeyframesを使う
keyframes
/* keyframes */
@keyframes MoveLeft {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}
@keyframes MoveLeft2 {
from {
transform: translateX(0);
}
to {
transform: translateX(-200%);
}
}
JSで調整
・画面サイズや文字の長さによってアニメーションの早さが変わってしまうことを防ぐ
ABCDEFG HIJKLMN
HTML
ABCDEFG HIJKLMN
CSS
.loop-text.-js {
overflow: hidden; /* はみ出したテキストは表示しない */
display: flex;
width: 100vw;
margin-inline: calc(50% - 50vw);
}
.loop-text.-js .loop-text__item {
flex-shrink: 0; /* 要素を縮めたくない */
white-space: nowrap; /* 要素を改行させない */
font-size: 60px;
font-style: italic;
font-weight: bold;
color: #555555;
letter-spacing: 0.05em;
&:nth-child(odd) {
animation: MoveLeft var(--tick-duration, 24s) var(--tick-delay, -12s) infinite linear;
animation-fill-mode: both;
}
&:nth-child(even) {
animation: MoveLeft2 var(--tick-duration, 24s) infinite linear;
animation-fill-mode: both;
}
}
@media screen and (min-width: 769px) {
.loop-text.-js .loop-text__item {
font-size: 120px;
}
}
・流れるアニメーションの動きはkeyframesを使う
keyframes
/* keyframes */
@keyframes MoveLeft {
from {
transform: translateX(100%);
}
to {
transform: translateX(-100%);
}
}
@keyframes MoveLeft2 {
from {
transform: translateX(0);
}
to {
transform: translateX(-200%);
}
}
・流れるアニメーションの動きはkeyframesを使う
JavaScript
class Main {
constructor() {
this.init();
}
init() {
this.copyText();
this.calculateLoopAnimationSpeed();
this.resizeRefresh();
}
//リサイズ時にアニメーションの速度を再計算
resizeRefresh() {
const target = document.body;
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
this.calculateLoopAnimationSpeed();
});
});
resizeObserver.observe(target);
}
//アニメーションの速度を計算してCSS変数に
calculateLoopAnimationSpeed() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
const distance = window.innerWidth;
const mql = window.matchMedia('(min-width: 801px)');
const time = mql.matches ? 18 : 9;
const speed = distance / time;
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
const total = tickElems.length - 1;
tickElems.forEach((el, i) => {
const elWidth = el.clientWidth;
const elTime = Math.floor(elWidth / speed);
el.style.setProperty('--tick-duration', `${elTime}s`);
el.style.setProperty('--tick-delay', `${elTime / -2}s`);
if (i === total) {
el.parentNode.classList.remove('no-tick');
}
});
});
}
//テキストをコピーする
copyText() {
const targets = document.querySelectorAll('.js-tick');
if (!targets.length) {
return;
}
targets.forEach((target) => {
const tickElems = target.querySelectorAll('.js-tick-item');
if (!tickElems.length) {
return;
}
let length = 0;
tickElems.forEach((el) => {
length += el.clientWidth;
el.insertAdjacentHTML('afterend', el.outerHTML);
if (length > window.innerWidth) {
return;
}
});
});
}
}
new Main();