【ブログ改造計画】WordPressのヘッダアニメーションをp5.jsで実装する【WordPress】
- ホーム
- 20220509_01
p5.js | home
エレキベア 確か前ゲーム数学を勉強していた時にも使用していたクマね 【ゲーム数学】第一回 p5.jsで学ぶゲーム数学「二次元ベクトル」マイケルみなさんこんにちは!マイケルです!エレキベアクマ〜〜〜マイケル今日から新しいシリーズとしてゲーム数学シリーズ を始めるよ!エレキベアす、数学クマか・・・!勉強は嫌クマ・・・!...localhost2021.01.17↑以前の記事
マイケル 使ったことがあったのと、今回のアニメーションは簡単な図形の組み合わせで実現できそうだったから使ってみたんだ! さっそく作り方をみていこう!! エレキベア 楽しみクマ〜〜〜 目次- p5jsの導入と動作確認
- 型定義のインストール
- p5.min.jsの配置と読み込み
- ヘッダーに挿入する
- ヘッダー全体に描画する
- アニメーションを実装する
- ビルのランダム生成
- 窓のランダム生成
- パーティクルの表示
- リサイズ処理
- 処理負荷対策
- フレームレートの設定とdeltaTimeの使用
- 画面に映っていない時は停止する
- p5jsの表示タイミングが遅い
- おわりに
p5jsの導入と動作確認
マイケル まず前提として、開発環境は下記のようにTypeScript、Stylus環境を構築しているものとして進めていきます。 【ブログ改造計画】公開中のブログをGit管理して開発環境を構築する【WordPress】マイケルみなさんこんばんは!マイケルです!エレキベアクマ〜〜〜マイケル今日は前回に引き続き、WordPress環境構築を進めていくよ!第二回ということで、・公開中のWordPressブログのGit...localhost2022.04.27↑開発環境構築の記事
エレキベア p5をTypeScriptで書くのは楽しみクマね 型定義のインストール マイケル まずはp5.jsの型定義を下記コマンドでインストールします。 また、WordPress環境に搭載されているjQueryも使用するため、こちらも合わせてインストールします。 // p5jsの型定義 npm install --save-dev @types/p5 // jqueryの型定義 npm install --save-dev @types/jquery↑TypeScriptとjQueryの型定義をインストール マイケル p5.js自体もnpm install出来るのですが、今回はwebpack等も使用しないため 別途ダウンロードして配置することとしました。 エレキベア あくまで型定義だけ出来るようにしたわけクマね マイケル インストールしたら下記のように型定義ファイルに定義するのみ! これによりtsファイル内で扱える様になります。 /** * p5js型定義 */ import module = require('p5'); export = module; export as namespace p5; declare global { interface Window { p5: typeof module, } } /** * jQuery型定義 */ import jqModule = require('jquery') export = jqModule; export as namespace jquery; declare global { interface Window { jquery: typeof jqModule, } }↑型を定義する エレキベア 導入は楽勝クマ〜〜〜 p5.min.jsの配置と読み込み マイケル 次にp5自身をダウンロードしてブログの任意の場所に配置します。 下記の公式サイトからp5.min.jsを選んでダウンロードしましょう。home | p5.js
エレキベア これが本体クマか・・・ マイケル 配置したらPHPファイル内で下記のように読み込みます。 // JavaScript追加 add_action('wp_enqueue_scripts', function() { wp_enqueue_script('eleki-custom-p5', get_stylesheet_directory_uri() . '/lib/js/p5.min.js'); wp_enqueue_script('eleki-custom-header', get_stylesheet_directory_uri() . '/src/js/header.js'); // こちらは自身で作成したjsファイル }); ↑jsファイルの読み込み マイケル これでp5の導入は完了です! エレキベア 早く使いたいクマ〜〜〜 ヘッダーに挿入する マイケル 導入が完了したら、簡単に動作確認してみましょう! 下記のようにコードを書いてブラウザで見てみます。 window.addEventListener("DOMContentLoaded", function(e) { // ヘッダー用のsketchを作成 const sketch = (p: p5) => { p.setup = () => { p.createCanvas(400, 400); }; p.draw = () => { p.background(220); p.ellipse(50, 50, 80, 80); } }; // ヘッダーに挿入 let element: HTMLElement | null = document.querySelector("#header"); if (element != null) { new p5(sketch, element); } }); ↑とりあえずCanvasを表示してみる ↑表示された! マイケル このように表示されれば導入は成功です! エレキベア (ヘッダがぶっ壊れたクマ・・・) ヘッダー全体に描画する マイケル 次はヘッダー全体にp5のCanvasを表示させてみましょう。 コードを下記のように修正して、ヘッダーのサイズからCanvasを生成し、 style指定をサイズ100%、object-fitをcover(領域全体表示)にします! /** * ヘッダークラス */ class Header { private static readonly ELEM_HEADER = "#header-in"; private static width: number; private static height: number; /** * ヘッダー表示処理 */ public static showHeader(): void { // sketch作成 const sketch = (p: p5) => { p.setup = () => { this.StartSketch(p); }; p.draw = () => { this.UpdateSketch(p); }; p.windowResized = () => { this.ResizedSketch(p); }; }; // ヘッダーに挿入 let element: HTMLElement | null = document.querySelector(Header.ELEM_HEADER); if (element != null) { new p5(sketch, element); } } /** * 開始処理 */ private static StartSketch(p: p5) { // Canvasサイズを取得 this.width = $(this.ELEM_HEADER).width(); this.height = $(this.ELEM_HEADER).height(); // Canvas設定 let canvas = p.createCanvas(this.width, this.height); canvas.style('position', 'absolute'); canvas.style('width', '100%'); canvas.style('height', '100%'); canvas.style('object-fit', 'cover'); // 初期表示 this.InitDrawCanvas(p); } /** * 更新処理 */ private static UpdateSketch(p: p5) { // TODO 適当な図形を描画 p.ellipse(50, 50, 80, 80); } /** * リサイズ処理 */ private static ResizedSketch(p: p5) { // サイズを再設定 this.width = $(Header.ELEM_HEADER).width(); this.height = $(Header.ELEM_HEADER).height(); p.resizeCanvas(this.width, this.height); // 再描画 this.InitDrawCanvas(p); } /** * 初期描画 */ private static InitDrawCanvas(p: p5) { // 黄色背景 p.background(255, 245, 0); // 黒背景 p.fill(34, 34, 34); p.rect(0, this.height / 2, this.width, this.height / 2); } } ↑ヘッダー全体にCanvasを表示させる jQuery(function() { // 読み込み完了時 window.addEventListener("DOMContentLoaded", function(e) { Header.showHeader(); }); })↑読み込みが完了したら表示させる ↑ええやん・・・ マイケル するとこのように全体に表示されることが確認できました。 これで土台は完璧です・・・! エレキベア これでやりたい放題クマ〜〜〜 p5.jsプログラミングガイド ¥3,520 Generative Design with p5.js - [p5.js版ジェ... ¥4,180アニメーションを実装する
マイケル あとは自由にアニメーションを実装していくのみ! p5.jsは多くのゲームエンジンと同じく開始処理、更新処理が用意されているので、 普段ゲーム開発を行っている皆さんなら簡単に作れると思います! エレキベア ゲームプログラミング感覚で出来るクマね マイケル コードは全部載せきれないので、作った順序に沿って 要所要所を載せていこうかと思います。 ビルのランダム生成 マイケル まずはビルを生成してみましょう。 今回は高さをランダムで決めてニョキっと生えるアニメーションにしたいので、 目的の高さをコンストラクタで受け取り、更新処理内で目的の高さまで伸ばすようにしてみます。 /** * ビルクラス */ class Bill { private p: p5; private readonly width: number; // 横幅 private readonly targetHeight: number; // 目的の高さ private readonly posX: number; // 出現させるX位置 private height: number; // 高さ private addHeight: number; // フレーム毎に加える高さ private totalTime: number; // 更新中の累計時間 constructor(p: p5, width: number, targetHeight: number, posX: number) { this.p = p; this.width = width; this.targetHeight = targetHeight; this.posX = posX; } public Start() { // ビルの情報を設定 this.height = 0; this.addHeight = 0.6 * (this.targetHeight/100); this.totalTime = 0; } public Update() { // 目的の高さになるまで加える if (!this.isTargetHeight()) { // 1秒間に120px伸びるとしてイージングを設定 let t = MathUtil.easeOutQuad((this.totalTime/1000)/(this.targetHeight/120)); this.height += this.addHeight * this.p.deltaTime * t; this.totalTime += this.p.deltaTime; } } public Draw() { // ビル描画 this.p.fill(34, 34, 34); this.p.rect( this.posX - (this.width / 2), // X: 原点が左上のため、指定位置から横幅の半分を引いた位置にする Header.height - this.height, // Y: 下から伸びるように見せるため、ヘッダー高さから縦幅を引いた位置にする this.width, this.height); } // 目的の高さに到達したか? public isTargetHeight(): boolean { return this.height > (this.targetHeight - 1.0); } }↑ビルクラスの作成 エレキベア シンプルな構成になっているクマね マイケル 伸ばす動きには勢いを付けたいのでイージング関数を使用しています。 こちらも簡単に実装できるので試してみてください!参考:イージング関数チートシート
// イーズイン public static easeInQuad(t: number) { return t * t; } // イーズアウト public static easeOutQuad(t: number) { return t * (2 - t); } public static easeOutQuart(t: number): number { return 1 - Math.pow(1 - t, 4); }↑イージング関数の実装 エレキベア アニメーションの鉄板クマね マイケル あとはこのビルクラスをランダムな高さを与える様に生成して 更新処理内で更新、描画するようにすれば完了です! /** * ビル生成処理 */ private static GenerateBills() { // 初期化フラグをOFF this.isInit = false; // ビル配列初期化 this.bills = []; // ビルのランダム値を設定 let billWidth: number = Math.max(80, Header.width / 10); // ビルの幅:ヘッダ幅/10(最小80px) let billSpace: number = billWidth*0.8; // ビルの間隔:幅*0.8で重ねる let billCount: number = Header.width / billSpace + 1; // ビルの数:間隔で割った数値+1 for (let i = 0; i < billCount; i++) { // ビル生成 let bill: Bill = new Bill( this.p, billWidth * MathUtil.getRandom(0.8, 1.2), // 横幅:若干ブレを持たせる Header.height * MathUtil.getRandom(0.35, 0.7), // 高さ:ヘッダ高さ*0.35〜0.7 i * billSpace); // 開始処理 bill.Start(); this.bills.push(bill); } // ランダムにシャッフルする this.bills.sort(()=> Math.random() - 0.5); // 初期化フラグをON this.isInit = true; }↑ランダムに生成する /** * 更新処理 */ private static UpdateSketch() { // 初期化していなければ処理を行わない if (!this.isInit || this.bills == null) { return; } // 更新処理 for (let i = 0; i < this.bills.length; i++) { this.bills[i].Update(); } // 描画処理 this.p.background(255, 245, 0); for (let i = 0; i < this.bills.length; i++) { this.bills[i].Draw(); } }↑更新・描画処理 マイケル ブラウザで確認するとこのようにニョキっと生えてくるはずです!! エレキベア これは楽しいクマ〜〜〜 窓のランダム生成 マイケル 次に生成したビルの中に窓を配置してみます。 今回は横は3列固定として、高さに入る分だけ配置してみました! // 窓の情報を設定 this.windowWidth = this.width/3 - this.width/4; // X方向に3つ設定するとしていい感じに調整 this.windowHeight = MathUtil.getRandom(30, 50); // 決め内で高さは決める this.windowHeightSpace = MathUtil.getRandom(10, 20); // 高さの間隔 this.windowHeightTopOffset = this.targetHeight/10; // オフセット this.windowCountX = 3; // X方向の数は3固定 this.windowCountY = this.targetHeight / (this.windowHeight+this.windowHeightSpace) + 1; // Y方向は入り切る数分 // 隠す窓のindexをランダムで決める this.windowHideArray = []; for (let i = 0; i < this.windowCountX*this.windowCountY; i++) { if (MathUtil.getRandom(0, 10) < 3) { // とりあえず3割 this.windowHideArray.push(i); } }↑ビルクラスの中で窓情報も設定 // 窓リスト描画 this.p.fill(242, 242, 242); let index = 0; for (let x = 0; x < this.windowCountX; x++) { for (let y = 0; y < this.windowCountY; y++) { // 非表示に設定されていない場合 if (this.windowHideArray.indexOf(index) === -1) { // 窓を描画 this.p.rect( this.posX + (x-1)*this.width/this.windowCountX - (this.windowWidth/2), // X: (-1,0,1)に横幅をかけて窓の半分の幅を引く (Header.height - this.height) + (this.windowHeightTopOffset) + y*(this.windowHeight+this.windowHeightSpace), // Y: ビルの位置+オフセット+個数分 this.windowWidth, this.windowHeight ) } index++; } }↑窓の描画処理 マイケル 上記を追加で実装すると、このように窓も表示されることを確認できます。 ランダムで消灯させているのがミソですね! エレキベア それっぽくなってきたクマね〜〜 マイケル あとは同様に丸い窓も生成するように実装しました! パーティクルの表示 マイケル 最後に、背景にシャボン玉のようなパーティクルを表示させてみましょう! 下記のように上下をスクロールするように移動させ、大きさが変化するようにすれば完了です! /** * パーティクル(ドット円)クラス */ class Particle { private p: p5; private x: number; private y: number; private r: number; private a: number; private initX: number; private upSpeed: number; private shakeSpeed: number; private shake: number; private scale: number; private frameCount: number; constructor(p: p5) { this.p = p; } public Start() { // ランダムで位置を設定 this.x = MathUtil.getRandom(0, Header.width); this.y = MathUtil.getRandom(0, Header.height); this.r = MathUtil.getRandom(12, 25); this.a = 0; this.initX = this.x; this.upSpeed = MathUtil.getRandom(0.03, 0.09); // 1ミリ秒ごとの移動距離 this.shakeSpeed = 0.003; this.shake = MathUtil.getRandom(0, 10); this.frameCount = Math.random(); } public Update() { this.frameCount += this.shakeSpeed * this.p.deltaTime; this.x = this.initX + this.shake * Math.sin(this.frameCount); // 上方向に移動させる this.y -= this.upSpeed * this.p.deltaTime; if (this.y < - this.r / 2) { this.y = Header.height; } // 徐々に大きくする this.scale = 1 - MathUtil.easeOutQuad(Math.max(0, this.y / (Header.height+100))); // フェードイン this.a = Math.min(255, this.a+50); } public Draw() { this.p.stroke(34, 34, 34, this.a); this.p.noFill() this.p.circle(this.x, this.y, this.r*this.scale); } }↑パーティクルクラスの作成 マイケル 横に揺らすのはsin関数を使用しています! こちらも定番ですね! エレキベア 三角関数はマジで使えるクマ〜〜 マイケル あとはこれを適当な数だけ生成すると、 下記のように背景で浮かぶシャボン玉が確認できます! ↑パーティクルが生成された(ビルのアニメーション開始タイミングも調整している) エレキベア レモンスカッシュみたいで美味そうクマ マイケル これで完成!! ・・・と思いきや、さすがにこの数だけ生成すると、古い端末ではカクカクだったため 数を減らして調整しました・・・。 ↑調整して完成! エレキベア 微炭酸になったクマね リサイズ処理 マイケル リサイズ処理についての補足になるのですが、 リサイズ検知の度に生成しなおすのはうざったいため、timerを使用して一定間隔で処理するのが現実的です。 /** * リサイズ処理 */ private static ResizedSketch() { // アニメーション停止フラグが設定されていたら処理を行わない if (this.isStopAnimation) { return; } // 100px以上変更していない場合には処理を行わない if (Math.abs(this.prevWidth - $(Header.ELEM_HEADER).width()) < 100) { return; } // timerを使用して一定秒数ごとに処理させる if (this.resizeTimer > 0) { window.clearTimeout(this.resizeTimer); } this.resizeTimer = window.setTimeout(() => { // サイズを再設定 Header.width = $(Header.ELEM_HEADER).width(); Header.height = $(Header.ELEM_HEADER).height(); this.p.resizeCanvas(Header.width, Header.height); // 横幅を保持 this.prevWidth = $(Header.ELEM_HEADER).width(); // ビルを再生成 this.InitBills(); }, 500) }↑timerを使用して一定間隔で処理を行う マイケル また、iOSでは何故かスクロールする度にリサイズ検知されてしまうという不具合が発生したため、横幅が一定の幅以上変更された場合にのみ行うよう対処してあります・・・。 エレキベア カナシマシマシクマ・・・。処理負荷対策
マイケル 最後におまけとして、処理負荷対策についても紹介しておきます。 エレキベア 負荷はある程度はありそうクマからね フレームレートの設定とdeltaTimeの使用 マイケル p5.jsではデフォルトで60FPSに設定されているため、 調整しておくことをおすすめします。アニメーションであれば30FPSあれば充分ですね。 // フレームレート設定 this.p.frameRate(30);↑フレームレート設定 エレキベア ゲームなら60欲しいところクマね マイケル そしてこちらは基本になりますが、動きにはdeltaTimeを使用してフレームレートが落ちた際にも速度は変わらないようにしておきます。 P5.jsに用意されていますが、単位がmsになっていたためそこは注意が必要です。 // 上方向に移動させる this.y -= this.upSpeed * this.p.deltaTime;↑deltaTimeを使用した移動 エレキベア ゲーム開発の定番クマね 画面に映っていない時は停止する マイケル そしてもう一点、画面に映っていない間はアニメーションを停止 させるようにしました。 こちらはスクロール範囲にターゲット(ヘッダー)が入っているかどうかでフラグを設定するようにしています。 // スクロール検知処理 $(window).on('load scroll', function() { // ヘッダーが要素から外れた時にアニメーションを停止させる ScrollUtil.addCallbackWhenVisible( '#header', () => Header.setIsStopAnimation(false), () => Header.setIsStopAnimation(true)); });↑スクロール検知処理 /** * スクロール関連 */ export class ScrollUtil { /** * 要素が画面に表示された時の処理を設定する * @param targetName 対象の要素名 * @param visibleCallback 画面に表示されている時の処理 * @param hideCallback 画面から表示されない時の処理 */ public static addCallbackWhenVisible(targetName: string, visibleCallback: () => void, hideCallback: () => void) { // スクロール範囲の取得 let scrollTop = $(window).scrollTop(); let scrollBottom = scrollTop + $(window).height(); // ターゲット範囲の取得 let targetTop = $(targetName).offset().top; let targetBottom = targetTop + $(targetName).height(); // ターゲットが画面内に入っているか? if (scrollBottom > targetTop && scrollTop < targetBottom) { visibleCallback(); } else { hideCallback(); } } }↑ターゲットがスクロール範囲に入っているか エレキベア なるほどクマ 確かに記事読んでいる間もずっと動いていたら大迷惑クマ マイケル 対策は今のところこれくらいしかしていないけど、 もし全然動作しない!みたいな声が出てきた時には、「フレームレートが一定以下だったら画像に差し替える」ような対策も検討しようかと思っています! エレキベア 問題ないことを祈るクマ・・・ p5jsの表示タイミングが遅い マイケル処理負荷対策とは少し話がずれるかもしれませんが、DOMContentLoadedで読みこんでいるのに表示タイミングが遅いような・・・と感じる場面が出てくると思います。 それはp5.jsのsetup関数がHTMLのload後に呼ばれるためです。 エレキベアなんと、そうだったのクマね・・・ マイケルp5.jsの方を変えるのは恐らく難しいと思うので、その場合はHTMLのload自体を早くするのがよいと思います。 lazyloadというJavaScriptライブラリを導入すれば画像読み込みを遅延させることができるので、こちらも検討してみてください! lazyload – GitHub エレキベア導入するのも簡単そうクマねおわりに
マイケル というわけで今回はp5でヘッダアニメーションを作成してみました! どうだったかな?? エレキベア こんなお手軽で高機能なライブラリがブラウザ上で使えるなんて驚いたクマ もっといろいろ作ってみたいクマね マイケル せっかくだからp5.jsでゲームを作ってみるのも面白そうだなと思ったよ! また機会があったら触ってみよう!! マイケル それでは今日はこの辺で! アデュー!! エレキベア クマ〜〜〜【ブログ改造計画】WordPressのヘッダアニメーションをp5.jsで実装する【WordPress】〜完〜
p5.jsプログラミングガイド ¥3,520 Generative Design with p5.js - [p5.js版ジェ... ¥4,180 WordPress関連JavaScriptフロントエンド関連アニメーションp5.js2022-05-11記事をSNSで共有する著者の各種アカウントフォローいただけると大変励みになります!関連記事【Three.js】カスタムシェーダーでトゥーン+背面法アウトラインを実装する2026-02-15【Three.js】Three.js入門 - シーン構築・モデル読み込み・ポストプロセスまで2026-02-15【Astro】Astroの使い方と複数UIフレームワーク(React、Vue、Svelte)を組み合わせるサンプル2026-02-01【Houdini21.0】3Dビル群っぽいブログヘッダー画像を作成する2026-01-10【Houdini21.0】Otisによる筋肉シミュレーションと筋肉情報転送機能の使い方【Otis Muscle and Tissue Simulation】2025-11-29【VSCode】ドラッグ&ドロップで画像ファイルをリサイズ・保存する拡張機能を作る2025-11-22【ゲーム数学】第十回 p5.js(+α)で学ぶゲーム数学「複素数とフラクタル」2025-11-02【書籍紹介】「コンピュータグラフィックス」に出てくる用語をまとめる【CGエンジニア検定】2024-07-13▲