コードエディター 本文
// canvasサイズ
<div id="sand-container" style="width: 320px; height: 90px; background: #1a1a1a; position: relative; overflow: hidden;">
</div>
カスタムJavaScript
(function() {
// --- 設定項目 ---
const containerId = 'sand-container'; // 砂を表示させるDIVのID
const textList = ["東海の", "小島の磯の", "白砂に","われ泣きぬれて","蟹とたはむる","- 石川啄木"]; // 表示する文字列
const particleCount = 4000; // 砂の粒の数
const sandColor = '#D8CEBA'; // 砂の色
const container = document.getElementById(containerId);
if (!container) return;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 枠内にフィットさせるためのスタイル
canvas.style.position = 'absolute';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
let width, height, particles = [];
let textIndex = 0;
class Particle {
constructor() {
this.init();
}
init() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.destX = this.x;
this.destY = this.y;
this.vx = 0;
this.vy = 0;
this.accel = 0.02 + Math.random() * 0.04;
this.friction = 0.85 + Math.random() * 0.1;
}
update() {
// 目的地に向かう物理演算
let dx = this.destX - this.x;
let dy = this.destY - this.y;
this.vx += dx * this.accel;
this.vy += dy * this.accel;
this.vx *= this.friction;
this.vy *= this.friction;
this.x += this.vx;
this.y += this.vy;
}
draw() {
ctx.fillStyle = sandColor;
ctx.globalAlpha = 0.8; // 少し透けさせて質感を出す
ctx.fillRect(this.x, this.y, 1.2, 1.2);
}
}
function resize() {
// 親要素のサイズを取得
width = canvas.width = container.offsetWidth;
height = canvas.height = container.offsetHeight;
}
function getTextPoints(text) {
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = width;
tempCanvas.height = height;
const fontSize = 40; // 好きなピクセル数に変更(フォントサイズ)
tempCtx.textAlign = 'center';
tempCtx.textBaseline = 'middle';
// フォントの書体
tempCtx.font = `bold ${fontSize}px "Yu Mincho", "YuMincho", "MS PMincho", serif`;
tempCtx.fillText(text, width / 2, height / 2);
const imageData = tempCtx.getImageData(0, 0, width, height).data;
const points = [];
const step = 1; // 密度調整
for (let y = 0; y < height; y += step) {
for (let x = 0; x < width; x += step) {
if (imageData[(y * width + x) * 4 + 3] > 128) {
points.push({x, y});
}
}
}
return points;
}
function updateText() {
const points = getTextPoints(textList[textIndex]);
// 砂に新しい目的地を割り当てる
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
if (i < points.length) {
// 文字の形へ
p.destX = points[i].x + (Math.random() - 0.5) * 3;
p.destY = points[i].y + (Math.random() - 0.5) * 3;
} else {
// 溢れた砂は下に溜まるように(重力演出)
p.destX = Math.random() * width;
p.destY = height + Math.random() * 50;
}
}
textIndex = (textIndex + 1) % textList.length;
}
function animate() {
ctx.clearRect(0, 0, width, height);
particles.forEach(p => {
p.update();
p.draw();
});
requestAnimationFrame(animate);
}
// 初期起動
window.addEventListener('resize', () => {
resize();
updateText();
});
resize();
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
animate();
setInterval(updateText, 4000); // 4秒ごとに切り替え
setTimeout(updateText, 500); // 初回実行
})();
カスタムCSS
