web3 のプロジェクトのウェブサイトを見つけました。背景はコードで構成された雨のエフェクトで、かなりクールに見えるので、この記事で解析してみます。
エフェクトの内容を注意深く見ると、エフェクトは 2 つのパートで構成されていることがわかります。
- エフェクトが始まると、文字で構成されたテキストが上から下に均等に生成され、透明から不透明に徐々に変化し、ウィンドウ全体に広がります。
- ウィンドウ全体に広がった後、各行の異なる列の文字の透明度が異なり、異なる速度で落ちるエフェクトが表示されます。
まず、キャンバスを使用してテキストマトリックスを上から下に均等に生成することを試してみましょう。全体のアイデアは、各文字のサイズに基づいてキャンバス全体を col * row の行列に切り替え、タイマーを使用して列ごとにコード文字を生成することです。
コードのアイデアは比較的単純です。
- 各描画サイクルで 1 行のコンテンツを描画し、次の行の文字のキャンバス座標を計算します。
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const allChars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~`|}{[]:;?><,./-=\\';
const chars = Array.from(allChars);
// 各文字のフォントサイズ
const fontSize = 16;
// 描画する2D情報
const dimensions = {
width: window.innerWidth,
height: window.innerHeight,
};
canvas.width = dimensions.width;
canvas.height = dimensions.height;
// 各行の文字数
const rows = Math.floor(dimensions.width / fontSize);
const currentHeights = Array.from({ length: rows }).fill(0);
const textColor = '#0F0';
const bgColor = 'rgba(0, 0, 0, 0.08)';
// 各行のコンテンツを描画する
function draw() {
context.fillStyle = textColor;
context.font = `${fontSize}px monospace`;
for (let i = 0; i < rows; i++) {
const x = i * fontSize;
const y = currentHeights[i] * fontSize;
context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);
// 次の行のy座標
currentHeights[i] = currentHeights[i] + 1;
}
}
setInterval(draw, 100);
次に処理する必要があるのは、次のようなキャンバスの画像です。
キャンバスの画像は上から下に徐々にぼやけていき、上部に行くほどテキストの透明度が高くなります。
これは、各描画サイクルの開始時に、キャンバス全体に透明度を持つ背景色を塗りつぶすことで実現できます。この処理により、
- 現在の X サイクルの描画の前に、背景色は前景色であり、ブラウザの色の混合計算に基づいて、テキストは透明な効果を示します。
- 現在の X サイクルの描画時、背景色は背景色であり、現在の行のテキストには影響を与えません。
以下は、主要なコードです。
const textColor = '#0F0';
const bgColor = 'rgba(0, 0, 0, 0.08)';
// 各行のコンテンツを描画する
function draw() {
// 透明度0.08の黒い背景をキャンバス全体に塗りつぶす
context.fillStyle = bgColor;
context.fillRect(0, 0, dimensions.width, dimensions.height);
// 現在の行のテキストを描画する
context.fillStyle = textColor;
context.font = `${fontSize}px monospace`;
for (let i = 0; i < rows; i++) {
const x = i * fontSize;
const y = currentHeights[i] * fontSize;
context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);
// 次の行のy座標
currentHeights[i] = currentHeights[i] + 1;
}
}
`
これで、基本的な効果が得られます。次に、最下部のテキストがキャンバスの境界を超えていないかどうかを判断し、超えている場合は上部から再び描画する必要があります。
// 各行のコンテンツを描画する
function draw() {
// 透明度0.08の黒い背景をキャンバス全体に塗りつぶす
context.fillStyle = bgColor;
context.fillRect(0, 0, dimensions.width, dimensions.height);
// 現在の行のテキストを描画する
context.fillStyle = textColor;
context.font = `${fontSize}px monospace`;
for (let i = 0; i < rows; i++) {
const x = i * fontSize;
const y = currentHeights[i] * fontSize;
context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);
// 次の行のy座標
currentHeights[i] = currentHeights[i] + 1;
// 上部から再開
if (currentHeights[i] * fontSize > dimensions.height) {
currentHeights[i] = 0;
}
}
}
見た目がまだ少し醜いので、各列が最初に上から下に表示されるコードをハイライト表示したり、非表示にしたりするなど、いくつかの最適化を追加します。
以下は、完全なコードです。
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const allChars =
'∅abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~`|}{[]:;?><,./-=\\';
const chars = Array.from(allChars);
// 各文字のフォントサイズ
const fontSize = 16;
// 描画する2D情報
const dimensions = {
width: window.innerWidth,
height: window.innerHeight,
};
canvas.width = dimensions.width;
canvas.height = dimensions.height;
// 各行の文字数
const rows = Math.floor(dimensions.width / fontSize);
const currentHeights = Array.from({ length: rows }).fill(0);
const textColor = '#0F0';
const highlightColor = '#FF55BB';
const bgColor = 'rgba(0, 0, 0, 0.08)';
// 各行のコンテンツを描画する
function draw() {
// 透明度0.08の黒い背景をキャンバス全体に塗りつぶす
context.fillStyle = bgColor;
context.fillRect(0, 0, dimensions.width, dimensions.height);
// 現在の行のテキストを描画する
context.fillStyle = textColor;
context.font = `${fontSize}px monospace`;
for (let i = 0; i < rows; i++) {
const x = i * fontSize;
const y = currentHeights[i] * fontSize;
const char = chars[Math.floor(Math.random() * chars.length)];
context.fillStyle = char === '∅' ? highlightColor : textColor;
context.fillText(char, x, y);
// 次の行のy座標
currentHeights[i] = currentHeights[i] + 1;
// 上部から再開
if (
currentHeights[i] * fontSize > dimensions.height &&
Math.random() > 0.95
) {
currentHeights[i] = 0;
}
}
}
setInterval(draw, 100);
#ブログ