ちょっと意味のないお遊びだが。一覧の1記事へリンクする際に、カーソルが上から順に食べ尽くしてから記事ページへ移動するアニメーション。
動作概要
一覧の表記サンプル
<div class="postlist02-title">タイトル<a href="<?php echo esc_url( get_permalink() ); ?>"><?php echo esc_html( get_the_title() ); ?></a></div>
<div class="postlist02-meta">
<span class="postlist02-date">DATE <a href="<?php echo esc_url( get_permalink() ); ?>"><?php echo esc_html( get_the_time( 'Y-n-j' ) ); ?></a></span>
<h1 class="categories02">CATEGORIES <?php the_category( ' ' ); ?> <?php the_tags( '/ TAGS ', ' ' ); ?></h1>
</div>タイトル・日付・カテゴリの行を、上から1行ずつカーソルが食べていく形になる
- タイトルを押すとアニメーション開始
- 黒い四角のカーソルが、タイトルの1文字目からアイコンも1つずつ食べる
- 6割を過ぎたらカーソルの色を赤に 食べ終わったら記事へ移動
- 食べている途中にほかのタイトルを押しても無視する(二重に動かないように)
- ブラウザの「戻る」で一覧に戻ったときは元に戻る
※このアニメーションが発火するのたタイトルのみ(DATEやCATEGRYは発火なし)
※食べられるのは1件の記事分のみ
※食べたところは「見えなくする」だけでテキスト文字は残っている。
※行の幅も高さも変わらないのでページがずれたりスクロールしたりしない
CSS(title-eat.css)
カーソルの色指定はご自由に。点滅の速さは CSS の .2s と、JS の BLINK_MS = 200 の2か所で決まっている。片方だけ変えると、点滅が途中で切れたり、移動が遅れたりするので注意。
/* =====================================================================
* 一覧のタイトルを押すと、カーソルが食べてから移動する(js/title-eat.js)
* ・食べたところは見えなくするだけ(visibility: hidden)。行の幅・高さは変わらない
* ===================================================================== */
.te-gone { visibility: hidden; }
.te-caret {
background: #1d1f28; /* ★ カーソルの色(オープニングと同じ) */
color: transparent !important;
text-decoration: none !important;
}
.te-red .te-caret { background: #c8312b; } /* ★ 6割を過ぎたらの色 */
.te-eating a { pointer-events: none; } /* 食べている途中は、ほかのリンクを押せないように */
/* 食べ終わったあとのカーソル(js の AFTER)。記事の上に重ねて置く */
.te-end {
position: absolute;
top: 0;
left: 0;
background: #1d1f28;
pointer-events: none;
}
.te-end--red { background: #c8312b; }
.te-end--blink { animation: te-blink .2s steps(1) 2; } /* ★ 2回点滅(1回 0.2秒。js の BLINK_MS と合わせる) */
@keyframes te-blink { 50% { opacity: 0; } }
JavaScript(title-eat.js)
下記のSELECTOR・ITEM・ROWS で指定しているクラス名は、一覧サンプルに合わせて設定している。実際には、各自設定するページ内のタイトルやサブタイトル等のクラス名に合わせて変更すること。
/* =====================================================================
* 一覧のタイトルを押すと、その記事のタイトルとカテゴリの行をカーソルが食べてから移動する(V05.6 2026-11-02)
* ・対象:トップページの一覧の .postlist02-title の中のリンク(読み込むのはトップだけ。functions.php)
* ・食べるのは押した記事の「タイトルの行」と「DATE・CATEGORIES の行」だけ。ほかの記事はそのまま
* ・食べたところは visibility: hidden で見えなくするだけなので、行の幅・高さは変わらない(ページがずれたりスクロールしたりしない)
* ・6割を過ぎたらカーソルが赤(オープニングと同じ)
* ・食べ終わったら、カーソルが点滅(または行頭へ戻る)してから移動する(AFTER)
* ・普通に動かすもの:⌘/Ctrl/Shift/Alt+クリック、中クリック、target="_blank"、動きを減らす設定の人
* ・ブラウザの「戻る」で戻ったとき(bfcache)は、食べたところを元に戻す
* ===================================================================== */
(function () {
'use strict';
var SELECTOR = '.postlist02-title a'; /* ★ 押すと食べるリンク */
var ITEM = '.postlist02-content, .postlist02'; /* ★ 1件分のかたまり(リンクから一番近いものを使う) */
var ROWS = '.postlist02-title, .postlist02-meta'; /* ★ かたまりの中で食べる行(上から順に食べる) */
var STEP = 18; /* ★ 1文字を食べる時間(ミリ秒) */
var MIN = 600; /* ★ 食べる時間の最短(文字が少ない記事でも、これだけはかける) */
var MAX = 1300; /* ★ 食べる時間の最長(長いタイトルでも、これより待たせない) */
var RED_AT = 0.6; /* ★ カーソルが赤になる割合 */
var HOLD = 150; /* ★ 食べ終わったあとの動きが終わってから移動するまでの間 */
var BLINK_MS = 200; /* ★ 食べ終わったあとの点滅1回の時間(css の .te-end--blink と合わせる) */
var AFTER = 'blink'; /* ★ 食べ終わったあとの動き:'blink' 最後の位置でカーソルが2回点滅/'home' カーソルがタイトルの行頭へスッと戻る/'' なし */
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var busy = false;
/* 記事の中の「食べる単位」を上から順に集める(文字は1文字ずつ span に包む。アイコンは丸ごと1つ) */
function units(item) {
var list = [];
var rows = item.querySelectorAll(ROWS);
Array.prototype.forEach.call(rows, function (row) {
var walker = document.createTreeWalker(row, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
acceptNode: function (n) {
if (n.nodeType === 1) {
if (/^(I|SVG|IMG)$/i.test(n.tagName)) { return NodeFilter.FILTER_ACCEPT; } /* アイコン(星・爆弾など) */
return NodeFilter.FILTER_SKIP;
}
return /\S/.test(n.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
}
});
var n;
while ((n = walker.nextNode())) {
list.push(n); /* アイコンはそのまま1つ、文字はあとで1文字ずつの span に置き換える */
}
});
/* 文字を1文字ずつ包む(絵文字などの2つで1文字も崩さないように Array.from) */
var out = [];
list.forEach(function (u) {
if (u.nodeType === 1) { out.push(u); return; }
var frag = document.createDocumentFragment();
Array.from(u.nodeValue).forEach(function (ch) {
var s = document.createElement('span');
s.className = 'te-ch';
s.textContent = ch;
frag.appendChild(s);
out.push(s);
});
u.parentNode.replaceChild(frag, u);
});
return out;
}
/* 食べ終わったあとの動き(AFTER)。カーソルを記事の上に重ねて置く(行の幅や高さは変えない) */
function after(item, us, done) {
if (!AFTER || !us.length) { done(); return; }
var first = us[0].getBoundingClientRect();
var last = us[us.length - 1].getBoundingClientRect();
var base = item.getBoundingClientRect();
if (getComputedStyle(item).position === 'static') { item.style.position = 'relative'; }
var c = document.createElement('span');
c.className = 'te-end' + (item.classList.contains('te-red') ? ' te-end--red' : '');
c.style.height = last.height + 'px';
c.style.width = Math.max(6, Math.round(last.height * 0.45)) + 'px';
var endX = last.right - base.left, endY = last.top - base.top; /* 最後に食べた字のすぐ右 */
var homeX = first.left - base.left, homeY = first.top - base.top; /* タイトルの1文字目 */
c.style.transform = 'translate(' + endX + 'px, ' + endY + 'px)';
item.appendChild(c);
if (AFTER === 'home') {
/* 行頭へスッと戻る */
c.animate(
[ { transform: 'translate(' + endX + 'px, ' + endY + 'px)' }, { transform: 'translate(' + homeX + 'px, ' + homeY + 'px)' } ],
{ duration: 260, easing: 'cubic-bezier(.65, 0, .35, 1)', fill: 'forwards' }
);
setTimeout(done, 320);
} else {
/* その場で2回点滅(1回 BLINK_MS。css の .te-end--blink と合わせる) */
c.classList.add('te-end--blink');
setTimeout(done, BLINK_MS * 2);
}
}
/* カーソルで食べる(経過時間の割合 × 単位の数 まで消す) */
function eat(item, done) {
var us = units(item);
var dur = Math.max(MIN, Math.min(MAX, us.length * STEP));
var t0 = null;
var cur = -1;
item.classList.add('te-eating');
function frame(now) {
if (t0 === null) { t0 = now; }
var p = Math.min(1, (now - t0) / dur);
var k = Math.floor(us.length * p);
item.classList.toggle('te-red', p >= RED_AT);
for (var i = Math.max(cur, 0); i < k && i < us.length; i++) {
us[i].classList.remove('te-caret');
us[i].classList.add('te-gone');
}
if (k < us.length) { us[k].classList.add('te-caret'); }
cur = k;
if (p < 1) {
requestAnimationFrame(frame);
} else {
after(item, us, function () { setTimeout(done, HOLD); });
}
}
requestAnimationFrame(frame);
}
document.addEventListener('click', function (e) {
var a = e.target.closest ? e.target.closest(SELECTOR) : null;
if (!a || !a.href) { return; }
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { return; }
if (a.target && a.target !== '_self') { return; }
if (reduce) { return; } /* 動きを減らす設定の人は、そのまま移動 */
var item = a.closest(ITEM) || a.parentNode;
e.preventDefault();
if (busy) { return; } /* 食べている途中に、別のタイトルを押しても無視 */
busy = true;
var url = a.href;
eat(item, function () { window.location.href = url; });
});
/* 「戻る」で戻ったとき:食べたところを元に戻す */
window.addEventListener('pageshow', function (e) {
if (!e.persisted) { return; }
Array.prototype.forEach.call(document.querySelectorAll('.te-end'), function (el) { el.parentNode.removeChild(el); });
Array.prototype.forEach.call(document.querySelectorAll('.te-gone, .te-caret, .te-eating, .te-red'), function (el) {
el.classList.remove('te-gone', 'te-caret', 'te-eating', 'te-red');
});
busy = false;
});
})();
また var AFTER = 'blink'; の値を変えることで食べ尽くしたあとの動作を変えることができる。
AFTER の値 | 食べ終わったあと | 移動までの時間 |
|---|---|---|
'blink' | 最後に食べた字のすぐ右にカーソルが残って、2回点滅 | 約0.4秒 + 0.15秒 |
'home' | カーソルがタイトルの行の先頭へスッと戻る(0.26秒) | 約0.3秒 + 0.15秒 |
'' | 何もしない | 0.15秒 |
PHP(functions.php)
if ( ! is_front_page() ) でフロントページではない場合は処理を終了。処理は最後の add_action( 'wp_enqueue_scripts', 'memo_enqueue_title_eat' ); なので、もしこのアニメーションを止めるのならこの行をコメントアウトすればいい。
各ファイルの格納先もここで指定する。'/css/title-eat.css' /js/title-eat.js
//---------------------------------------//
// 一覧のタイトルを押すと、その記事のタイトルとカテゴリの行をカーソルが食べてから移動する
// ・js/title-eat.js と css/title-eat.css。トップページだけ読み込む
// ・やめるときは下の add_action の1行をコメントアウト
//---------------------------------------//
function memo_enqueue_title_eat() {
// トップページ(2ページ目以降も含む)だけで読み込む
if ( ! is_front_page() ) {
return;
}
wp_enqueue_style(
'memo-title-eat',
get_template_directory_uri() . '/css/title-eat.css',
array(),
filemtime( get_template_directory() . '/css/title-eat.css' )
);
wp_enqueue_script(
'memo-title-eat',
get_template_directory_uri() . '/js/title-eat.js',
array(),
filemtime( get_template_directory() . '/js/title-eat.js' ),
true
);
}
add_action( 'wp_enqueue_scripts', 'memo_enqueue_title_eat' );格納先は各自テーマの環境に合わせる。サンプルはtitle-eat.cssはCSSフォルダ、title-eat.jsはjsフォルダに格納。変更はfunctionで設定変更すればいい。
表示環境によって動きの見え方が異なる場合があるため、本番に反映する前に確認してね。
NOTICES
- 記事内容は実装させたものがほとんどですが自己責任で参考にしてください。