タグ検索:愛知 , web制作 , a-blogcmsの小技

このエントリーはa-blog cms Advent Calendar 2025の21日目の記事です。

11月21日に開催された「a-blog cms Training Camp 2025」でご紹介させていただいた、弊社が開発している「SWIPE LP」テーマを制作する際に、アップロードした動画のposter画像を生成する方法を考えましたので、ご紹介させていただこうと思います。

「SWIPE LP」テーマとは


「SWIPE LP」テーマとはスマートフォンでの操作に最適化された、スワイプ形式のランディングページ用テンプレートです。1画面ずつ情報を見せる構成なので、ストーリーに沿ってスムーズに閲覧していただける点が特徴となります。離脱を抑えながら最後まで読み進めてもらいやすく、SNSのような操作感から若年層やスマートフォン利用が中心のユーザーと相性の良い形式です。

スマートフォンの場合は、真ん中のスライド部分が表示され、左右カラムの情報はメニューの中に格納されます。PCの場合は、真ん中の部分がスワイプ可能で、左右カラムの情報は固定で表示されます。

1枚のカード(コンテンツ)はエントリーで構成されており、動画・画像・ブロックエディタがレイヤー構造になっています。この様なエントリーを複数並べることで、LPページを構成する少し変わったテーマになります。

・「SWIPE LP」テーマ サンプルサイト
https://www.ideasource.jp/swipelp/

アップロードした動画からposter画像を生成する

では、早速ですが動画のカスタムフィールドの設定から、poster画像の生成まで、順番にご説明させていただきます。
最後にまとめたコードも一式掲載をしておきますので、ぜひチェックしてみてください。

エントリーの編集画面

動画用メディアカスタムフィールド、プレビュー用<video>タグ、poster用画像カスタムフィールドの設置

a-blog cmsの仕様では、メディアのカスタムフィールドに動画をアップロードすると、エントリー編集画面には「動画アイコン」が表示され動画自体は表示されませんが、今回の処理ではposter画像を生成するために動画自体を利用する必要がありますので、ファイルをアップロードするカスタムフィールドに加えて、動画のプレビューも設置が必要となります。

まずはエントリーの編集画面に必要な下記の3つ要素を追加します。
  • 動画アップロード用のカスタムフィールド(メディア)
  • 動画プレビュー用の<video>タグ
  • poster画像用のカスタムフィールド(画像)
操作の流れ
  1. 動画ファイルをメディアで登録しエントリーを保存します。
  2. 再度エントリーの編集画面を開くとプレビュー箇所に動画が表示されます。
  3. プレビュー動画の下にある「動画からポスター画像を生成」ボタンを押すと、jsの処理が動いてposter用の画像がカスタムフィールドの値に設定されます。(動画の1フレーム目をキャプチャして画像を生成します)
  4. その状態でエントリーを保存するとposter画像がカスタムフィールドに登録されます。



poster画像の生成は「動画からポスター画像を生成」ボタンをクリックすると、プレビュー用の動画からcanvas機能を使いpngファイルを生成します。



生成されたpngファイルは、そのままposter画像用のカスタムフィールドに値が設定されます。設定の処理が終わったら説明文言が切り替わりますので、その状態で「保存」をしてください。ちなみに生成されたposter画像は画面に表示させたくないため(プレビューと同じ画面なので)、cssで非表示にしています。



poster画像が設定されている状態で、エントリー編集画面を開くと「ポスター画像は登録されています。」に表示が切り替わります。この状態になっていたらエントリーの編集画面への実装は完了となります。poster画像を作り直したいときは、一度「ポスター画像を削除」にチェックを入れてエントリーを保存してから、再度エントリーの編集を行なってください。

エントリー編集画面のテンプレートの記述

エントリーのカスタムフィールドの設定とposter画像の設定をおこなっています。poster画像は軽量にするために横幅を200pxにしていますがお好みで変更してください。要素のIDなども自由に変えていただいてOKですが、jsと関連付いている所がありますのでご注意ください。また、jsをhtmlのテンプレートに直接記述する場合は「{}」にエスケープが必要な場合があります。ご利用のテンプレートに合わせて調整してください。

■ エントリー編集画面のカスタムフィールド

<table class="acms-admin-table-admin-edit cstm-admin-table-admin-edit">
	<tbody>
		<tr>
			<th>
				<span>動画</span>
			</th>
			<td>
				<div class="acms-admin-flex-column" id="swipelp-movwrapper">
					<div class="js-media-field" id="swipelp-moviewrapper">
						<div class="js-droparea" data-thumbnail="{swipelp_video@thumbnail}" data-type="file" data-thumbnail-type="{swipelp_video@type}" data-width="200px" data-height="200px"></div>
						<p class="js-text acms-admin-text-danger" style="display:none">許可されていないファイルのため挿入できません。</p>
						<div class="acms-admin-margin-top-mini">
							<button type="button" class="js-insert acms-admin-btn" data-type="file">メディアを選択</button>
						</div>
						<input type="hidden" name="swipelp_video" class="js-value" value="{swipelp_video}" />
						<input type="hidden" name="field[]" value="swipelp_video" />
						<input type="hidden" name="swipelp_video:extension" value="media" />
					</div>
				</div>

				<div class="acms-admin-flex-column cstm-movie-box">
					動画プレビュー<br>
					<video src="%{ARCHIVES_DIR}{swipelp_video@path}" controls
					mutedcontrolslist="nodownload"id="swipeli_video" class="acms-admin-img-responsive" width="200px"></video>
				</div>

				<div class="cstm-movie-box">
					ポスター画像は登録されています。<br>
					<div class="acms-admin-form-checkbox">
						<input type="checkbox" name="swipelp_video_image@edit" value="delete" id="input-checkbox-posterImg-delete" />
						<label for="input-checkbox-posterImg-delete">
							<i class="acms-admin-ico-checkbox"></i>
							ポスター画像を削除
						</label>
					</div>

					<img src="%{ARCHIVES_DIR}{swipelp_video_image@path}" class="js-img_resize_preview" alt="{swipelp_video_image@alt}" style="display:none" />
					<input type="hidden" name="swipelp_video_image@old" value="{swipelp_video_image@path}" />
				</div>
				<div class="cstm-movie-box">
					<a href="javascript:void(0);" onclick="generatePoster()" class="acms-admin-btn" style="width:200px;margin: 0.5em 0;padding:0.5em;">動画からポスター画像を生成</a><br>
					<div id="posterImageView">ポスター画像が登録されていません。<br>生成ボタンを押して画像を設定してください。</div>
					<div id="posterImageView-all" style="display:none">ポスター画像が設定されました。<br>エントリーを『保存』してください。</div>
				</div>
				<canvas id="canvas" style="display: none;"></canvas>
				<img id="posterImage" alt="Generated Poster" style="display: none; max-width: 200px;">
				<input type="file" name="swipelp_video_image" size="20" class="js-img_resize_input" id="swipelp_video_image_poster" style="display:none" />
				<input type="hidden" name="field[]" value="swipelp_video_image" />
				<input type="hidden" name="swipelp_video_image:extension" value="image" />
				<input type="hidden" name="swipelp_video_image@width" value="200" />
				<input type="hidden" name="swipelp_video_image@filename" value="" />
			</td>
		</tr>
	</tbody>
</table>

続いてjavascriptの処理になります。
こちらがプレビュー用の動画から、動画の1フレーム目をcanvasを利用してpngにし、画像のカスタムフィールドに値をセットする処理になります。
poster画像のカスタムフィールドの種類を、メディアではなく画像のカスタムフィールドにしている理由は、画像のカスタムフィールドではカスタムフィールドの<input>に値が設定されていれば、エントリー保存時に画像のアップロードが出来ますので、今回の様な処理の中でも簡単に実装が可能になります。

■エントリー編集画面のjavascript

<script>
const video = document.getElementById('swipeli_video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const posterImage = document.getElementById('posterImage');
const hiddenInput = document.getElementById('swipelp_video_image_poster');
const posterImageView = document.getElementById("posterImageView");
const posterImageViewAll = document.getElementById("posterImageView-all");

function generatePoster() {
	// readyStateが1 (HAVE_METADATA) 未満の場合、メタデータが読み込まれていない
	if (video.readyState < HTMLMediaElement.HAVE_METADATA) {		
		// loadedmetadataイベントを一度だけ待ち、読み込み完了後にgeneratePosterを再実行
		video.addEventListener('loadedmetadata', generatePoster, { once: true });
		return;
	}
	
	// seekedイベントの二重登録を防ぐため、一度解除
	video.removeEventListener('seeked', onSeeked);
	
	// seekedイベント登録
	video.addEventListener('seeked', onSeeked, { once: true });
	
	// 1秒目にシーク
	video.currentTime = 1;
}

// seekedイベントの処理(修正版)
function onSeeked() {
	// 元の動画サイズ
	const originalWidth = video.videoWidth;
	const originalHeight = video.videoHeight;
	
	// 縮小後の幅を200pxに固定
	const targetWidth = 200;
	const scale = targetWidth / originalWidth;
	const targetHeight = originalHeight * scale;
	
	// キャンバスサイズを設定
	canvas.width = targetWidth;
	canvas.height = targetHeight;
	
	// 動画フレームを縮小して描画
	ctx.drawImage(video, 0, 0, targetWidth, targetHeight);
	
	// Blobとしてエクスポート(PNG形式)
	canvas.toBlob((blob) => {
		// Fileオブジェクトを作成(ファイル名を指定)
		const file = new File([blob], 'poster.png', { type: 'image/png' });
		
		// DataTransferを使用してFileListを作成
		const dataTransfer = new DataTransfer();
		dataTransfer.items.add(file);
		
		// input[type="file"]のfilesプロパティに設定
		hiddenInput.files = dataTransfer.files;
		
		// 変更イベントを発火
		hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
		
		// プレビュー表示(Blob URLを使用)
		const blobUrl = URL.createObjectURL(blob);
		posterImage.src = blobUrl;
		
		// メモリリーク防止
		setTimeout(() => URL.revokeObjectURL(blobUrl), 10000);
		
		// posterImage要素のスタイルを設定
		if (posterImageView) {
			posterImageView.style.display = "none";
		}
		
		// posterImage-all要素のスタイルを設定
		if (posterImageViewAll) {
			posterImageViewAll.style.display = "block";
		}
	}, 'image/png', 0.95);  // 品質を95%に設定
}
</script>

エントリーの表示画面

poster画像のパスは%{ARCHIVES_DIR}になります。動画ファイルに使っているメディア用の%{MEDIA_ARCHIVES_DIR}と間違えない様にしてください。poster画像の設定は、画像ファイルの設定がある場合のみ出力する様にしています。

■ エントリーの表示画面

<!-- BEGIN_IF [{swipelp_video@type}/eq/file] -->
	<video src="%{MEDIA_ARCHIVES_DIR}{swipelp_video@path}"<!-- BEGIN_IF [{swipelp_video_image@path}/nem] --> poster="%{ARCHIVES_DIR}{swipelp_video_image@path}"<!-- END_IF --> preload="none" class="acms-top-0 acms-left-0 acms-z-0" width="100%" height="100%" loop autoplay muted playsinline></video>
<!-- END_IF -->

まとめ

カスタムフィールドでアップロードした動画ファイルの1フレーム目から、poster画像を設定する方法を考えてみました。
動画はYouTubeにアップロードをする事で手間を省くことも多いですが、自サーバにアップする際にはposter画像のことも考えなくてはいけない場合があると思います。そんな時には、是非こちらの内容を参考にしていただけると幸いです。

今回も少し変わった内容だったかもしれませんが、実際の案件にも使えると思います。
今後もこの様なTIPSもご紹介させていただきますので、皆さまのa-blog cmsでのサイト制作に役立てていただければ幸いです。
では皆さま、良いクリスマスを、そして良いお年をお迎えください。


このエントリーはa-blog cms Advent Calendar 2024の22日目の記事です。

以前こちらのブログ「a-blog cmsのキャッシュクリアボタンを追加で実装する方法」でご紹介した、a-blog cmsのキャッシュを消すボタンをログイン後の画面に追加表示する方法ですが、今回はこちらの機能に少しだけコードを追加して、さらに便利になる方法を考えてみました。

a-blog cmsの強力なキャッシュ機能

Webサイトはいろいろな方法でキャッシュ(一時ファイル)が生成され、高速化やデータをミニマムにするための処理がおこなわれています。ブラウザのキャッシュは有名ですが、a-blog cmsにも強力なキャッシュ機能が用意されており、「ページ キャッシュ」「テンプレート キャッシュ」「コンフィグ キャッシュ」「カスタムフィールド キャッシュ」「モジュール キャッシュ」「一時的なキャッシュ」と機能に合わせた複数のキャッシュが用意されています。

・a-blog cmsのキャッシュ機能
https://developer.a-blogcms.jp/document/cache/cache.html

便利なショートカットでらくらくキャッシュクリア

このキャッシュ機能が働くことで高速に処理され普段は良いことばかりなのですが、日々業務でWeb制作を行なっているとキャッシュが効いていることで、修正した部分が反映されず確認時にミスが起こることがあります。

もちろんa-blog cmsには対策として、ブログ・カテゴリー・エントリーなど管理画面で編集作業を行うと、自動的にキャッシュをクリアする機能が備わっていますので、普段は気にすることなく運用が可能になっておりますし、管理ページのダッシュボードにてボタン操作でキャッシュをクリアすることも出来ますので、特別困るというものではございません。

ただ、これほど便利なa-blog cmsでもテンプレートのみを編集した場合は、キャッシュがクリアされず変更内容が反映されません。a-blog cmsのテンプレートファイルはhtmlになりますので、テンプレートの編集時にシステムが動作しないからです。そのため管理ページのダッシュボードで毎回キャッシュをクリアする操作が必要となります。

この様な理由から、以前のブログでは手軽に操作ができる様に、ログイン後のページにキャッシュクリアボタンを追加したのですが、今回はさらにそのボタンをキーボードのショートカットで動作する様にしてみようと思います。

ということで、さっそく以前ご紹介したcacheclear.htmlを少しだけ手直しします。
まずはおさらいから。以前は下記の様なコードを書きました。

■ cacheclear.html(修正前)

<!-- キャッシュクリア用 cacheclear.html -->
<!-- BEGIN_MODULE Touch_Login -->
@section("dashboard-clear-cache")
<!-- BEGIN_MODULE Touch_SessionWithCompilation -->
<div id="adminBox" class="clearfix js-dragAdminBox_disable normalBox">
<form action="" method="post">
	<table class="clearfix js-dragAdminBox_disable normalBox" style="border:none;margin:0;padding:0;font-size:13px;">
	<tbody>
		<tr>
			<td>
				<div class="acms-admin-form-checkbox">
				<input type="checkbox" id="input-checkbox-cashe-page" name="target[]" value="page" checked />
				<label for="input-checkbox-cashe-page" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->ページ<!--/T--></label>
				</div>
				
				<!-- BEGIN_MODULE Touch_RootBlog -->
				<div class="acms-admin-form-checkbox">
				<input type="checkbox" id="input-checkbox-cashe-template" name="target[]" value="template" checked />
				<label for="input-checkbox-cashe-template" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->テンプレート<!--/T--></label>
				</div>
		
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-config" name="target[]" value="config" />
					<label for="input-checkbox-cashe-config" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->コンフィグ<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-field" name="target[]" value="field" />
					<label for="input-checkbox-cashe-field" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->カスタムフィールド<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-module" name="target[]" value="module" />
					<label for="input-checkbox-cashe-module" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->モジュール<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-temp" name="target[]" value="temp" />
					<label for="input-checkbox-cashe-temp" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->一時的なデータ<!--/T--></label>
				</div>
				<!-- END_MODULE Touch_RootBlog -->
				
				<div style="position: relative;display: inline-block;">
					<input type="hidden" name="field[]" value="target" />
					<input type="submit" name="ACMS_POST_Cache" value="<!--T-->キャッシュをクリア<!--/T-->" class="acms-admin-btn-admin"/>
					<input type="hidden" name="forcing" value="true" />
				</div>
			</td>
		</tr>
	</tbody>
	</table>
</form>
 
</div>
<!-- END_MODULE Touch_SessionWithCompilation -->
@endsection
<!-- END_MODULE Touch_Login -->

キャッシュをクリアするためのボタンを追加するコードです。
こちらの内容を記述するだけで、キャッシュクリアボタンを好きな場所に追加することができます。

では、上記のソースコードを一部修正して、特定のショートカットでjsイベントを発火するために処理を追加します。
jsを記述をする場所は「Touch_SessionWithCompilation」の中であれば、ログイン時に編集者以上の権限の場合にしか展開されないため安心です。

今回のサンプルはショートカット「ctrl(command) + shift + E」で実行される様にしてみました。私はブラウザ「Google Chrome」を使っていますので、ハード再読み込み「ctrl(command) + shift + R」の隣のキー(E)でa-blog cmsのキャッシュクリアとしてみました。ちなみにこのショートカットキーは一例ですので、お好みのキーに変更して使ってください。ショートカットキーを変更する際は、下記コード6行目の条件判定の値を変更すればOKだと思います。

<!-- ショートカットで発火 -->
<script>
document.addEventListener('keydown', (e) => {
	//ctrl(command)+shift+E
	if (((e.ctrlKey && !e.metaKey) || (!e.ctrlKey && e.metaKey)) && e.shiftKey && e.key.toLowerCase() === 'e') {
		$('#header-cacheclear-btn').trigger('click');
	}
});
</script>

そして45行目の「<input type="submit" name="ACMS_POST_Cache" value="<!--T-->キャッシュをクリア<!--/T-->" class="acms-admin-btn-admin"/>」に「id="header-cacheclear-btn"」を付与して、追加したjsからボタンクリックを発火するための指定をします。

<input type="submit" name="ACMS_POST_Cache" value="<!--T-->キャッシュをクリア<!--/T-->" class="acms-admin-btn-admin" id="header-cacheclear-btn"/>

そして修正後のコードがこちら

■ cacheclear.html(修正後)

<!-- キャッシュクリア用 cacheclear.html -->
<!-- BEGIN_MODULE Touch_Login -->
@section("dashboard-clear-cache")
<!-- BEGIN_MODULE Touch_SessionWithCompilation -->
<div id="adminBox" class="clearfix js-dragAdminBox_disable normalBox">
<form action="" method="post">
	<table class="clearfix js-dragAdminBox_disable normalBox" style="border:none;margin:0;padding:0;font-size:13px;">
	<tbody>
		<tr>
			<td>
				<div class="acms-admin-form-checkbox">
				<input type="checkbox" id="input-checkbox-cashe-page" name="target[]" value="page" checked />
				<label for="input-checkbox-cashe-page" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->ページ<!--/T--></label>
				</div>
				
				<!-- BEGIN_MODULE Touch_RootBlog -->
				<div class="acms-admin-form-checkbox">
				<input type="checkbox" id="input-checkbox-cashe-template" name="target[]" value="template" checked />
				<label for="input-checkbox-cashe-template" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->テンプレート<!--/T--></label>
				</div>
		
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-config" name="target[]" value="config" />
					<label for="input-checkbox-cashe-config" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->コンフィグ<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-field" name="target[]" value="field" />
					<label for="input-checkbox-cashe-field" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->カスタムフィールド<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-module" name="target[]" value="module" />
					<label for="input-checkbox-cashe-module" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->モジュール<!--/T--></label>
				</div>
				
				<div class="acms-admin-form-checkbox">
					<input type="checkbox" id="input-checkbox-cashe-temp" name="target[]" value="temp" />
					<label for="input-checkbox-cashe-temp" style="font-weight: normal;"><i class="acms-admin-ico-checkbox"></i><!--T-->一時的なデータ<!--/T--></label>
				</div>
				<!-- END_MODULE Touch_RootBlog -->
				
				<div style="position: relative;display: inline-block;">
					<input type="hidden" name="field[]" value="target" />
					<input type="submit" name="ACMS_POST_Cache" value="<!--T-->キャッシュをクリア<!--/T-->" class="acms-admin-btn-admin" id="header-cacheclear-btn"/>
					<input type="hidden" name="forcing" value="true" />
				</div>
			</td>
		</tr>
	</tbody>
	</table>
</form>
 
<!-- ショートカットで発火 -->
<script>
document.addEventListener('keydown', (e) => {
	//ctrl(command)+shift+E
	if (((e.ctrlKey && !e.metaKey) || (!e.ctrlKey && e.metaKey)) && e.shiftKey && e.key.toLowerCase() === 'e') {
		$('#header-cacheclear-btn').trigger('click');
	}
});
</script>

</div>
<!-- END_MODULE Touch_SessionWithCompilation -->
@endsection
<!-- END_MODULE Touch_Login -->

テンプレートキャッシュのクリアはルートブログだけの処理になっています

もともとa-blog cmsの管理ページに用意されたキャッシュクリアボタンは、ルートブログの場合はすべて表示されますが、子ブログ以下の階層では「ページ キャッシュ」のみとなりますので、今回仕様は合わせております。
したがってルートブログ以外では、今回のショートカットでも「ページ キャッシュ」のみのキャッシュクリアとなり、テンプレートキャッシュはクリアできませんのでご注意ください。もちろん上記コードの「Touch_RootBlog」の部分を変更すれば対応可能だとは思いますが、ルートブログの管理者以外でも操作が可能になりますので、どの様に実装されるかはご自身の判断と責任でお願いいたします。

まとめ

今回はa-blog cmsのキャッシュを手軽に消す方法を考えてみました。
小ネタになりましたが、ちょっとしたことを便利にするだけでも作業効率って上がる場合があります。テンプレートのhtmlをガツガツ直しながら作業をしている時は、何度も何度もキャッシュクリアしている日もありますし。まあ開発中はキャッシュ機能をオフにするという考え方もありますけどね…。

少し変わった内容だったかもしれませんが、個人的にはヘンテコで面白いなと思ったり…。この様なTIPSは今後もご紹介させていただきますので、皆さまのa-blog cmsでのサイト制作に役立てていただければ幸いです。

最後におまけ…2025年Youtubeチャンネル始めます!



この度2025年から「アイデアソースの小さなつづら」というYoutubeチャンネルを始動することになりました。
こちらのチャンネルでは、弊社が愛用しているa-blog cmsのお話や、アプリやハードウェアの紹介、写真・動画(カメラやレンズ)のお話など、作業の合間に息抜きで見ていただける様な手軽な話題をご紹介していきます。

今回はチャンネル始動への意気込みを兼ねて、ご挨拶動画をアップいたしました。(というか、これもうすでに始まっている?)
ぜひa-blog cmsに興味のある方は「チャンネル登録」してお待ちください。宜しくお願いいたします。