配信が終わったあとに、「今日は何時から何時まで、何を話したか」を一枚の絵にする。そういう絵を、同じ形で作れるようにしてみました。
作ったのは、ひとつの HTML ファイルです。開くと入力欄が四つ出ます。そこに配信タイトル・配信日・開始時刻・終了時刻を入れて、ボタンを押すと、1920 × 1080 の PNG が保存されます。OBS のブラウザソースに読ませれば、そのまま配信の画面に出せます。
配信のあとの作業は、だいたい決まっています。だから、決まった形の一枚を、決まった手順で出せると楽なのではないかと思って作りました。
以下は、わたくしが実際に書いて、手元で動かして、OBS に読み込んで表示を確かめたところまでの話です。確かめられていないことは書きません。
作ったもの
出来上がるのは、次のような一枚です。
- いちばん上に、辛子色の細い線が一本入ります。
- その下に「配信タイトル:」という見出しと、入力したタイトル。長いときは自動で折り返し、字の大きさを四段階に分けて、できるだけ大きく出します。三行に収まらない分は「…」で切ります。
- 画面の真ん中あたりに、上下を分ける細い線。
- 下の段に「配信日:」「開始時刻:」「終了時刻:」の三つを、左から順に、大きく並べます。
背景は濃い紺に近い色で、透けさせずに塗っています。明るい映像の上に出しても、暗い映像の上に出しても、字の下に必ずこの色が来るようにするためです。
入れるのは四つだけです。配信タイトル、配信日、開始時刻、終了時刻。この四つにしたのは、配信のあとに残しておきたいものが、だいたいこの四つだと思ったからです。どれかが空だと、絵は描かずに「配信タイトルを入れてください。」のように知らせます。
配信日は、入力した日付から「2026年9月21日(月)」の形に直して出します。曜日も入れます。時刻は 24 時間の「21:00」の形です。
書いている途中で、下の絵がその場で変わります。保存する前に、どんな一枚になるかを見て確かめられます。出してから「しまった」とならないように付けたものです。見本の内容に戻すボタンも置いてあります。押すと、タイトルは「はじめての雑談配信」、日付は今日、時刻は 21:00 と 22:30 に戻ります。
保存されるファイルの名前は、「配信ふりかえり_2026-09-21.png」のような形になります。日付が入っているので、同じフォルダに何枚たまっても、並べたときに区別が付きます。
なお、この記事には見本の PNG を置いていません。撮っていないものを置かないと決めているからです。代わりに、ファイルをお手元で開くと、同じ絵が画面の中に出ます。それを見てから保存してください。
そのまま置けるコード
下の囲みを、そのまま一つのファイルにしてください。名前は furikaeri.html でも koukai.html でも構いません。
中身は HTML と CSS と JavaScript だけです。外から何かを読み込む行はありません。ですから、一度ファイルとして置いてしまえば、あとはネットにつながっていなくても動きます。
<!DOCTYPE html>
<html lang=’ja’>
<head>
<meta charset=’UTF-8’>
<meta name=’viewport’ content=’width=device-width, initial-scale=1’>
<title>配信のふりかえり画像を作る</title>
<style>
*{box-sizing:border-box;}
body{
margin:0;
padding:32px 20px 80px;
background:#0C0F16;
color:#F2F6FB;
font-family:’Hiragino Sans’,’Yu Gothic’,’Noto Sans JP’,’Meiryo’,sans-serif;
line-height:1.7;
}
.wrap{max-width:1000px;margin:0 auto;}
h1{font-size:22px;margin:0 0 10px;}
.lead{margin:0 0 32px;font-size:14px;color:#8FA3BF;}
.fields{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px 24px;}
.wide{grid-column:1 / -1;}
label{display:block;font-size:13px;color:#8FA3BF;margin-bottom:8px;}
input{
width:100%;padding:12px 14px;font-size:16px;font-family:inherit;
color:#F2F6FB;background:#1A2030;
border:1px solid #2A3346;border-radius:6px;
}
input:focus{outline:2px solid #E8B93B;outline-offset:1px;}
.buttons{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin:32px 0 10px;}
button{font-family:inherit;font-size:15px;font-weight:700;padding:12px 22px;border:0;border-radius:6px;cursor:pointer;}
.primary{background:#E8B93B;color:#12161F;}
.ghost{background:transparent;color:#F2F6FB;border:1px solid #2A3346;}
.message{font-size:13px;color:#8FA3BF;min-height:1.7em;margin:0 0 20px;}
.message.alert{color:#F0A08A;}
.preview{border:1px solid #2A3346;border-radius:8px;overflow:hidden;background:#12161F;}
canvas{display:block;width:100%;height:auto;}
@media (max-width:640px){
.fields{grid-template-columns:1fr;}
}
</style>
</head>
<body>
<div class=’wrap’>
<h1>配信のふりかえり画像を作る</h1>
<p class=’lead’>はじめは見本の内容が入っています。書きかえると、下の絵がすぐ変わります。できあがりは 1920 × 1080 の PNG です。</p>
<div class=’fields’> <div class=’wide’> <label for=’title’>配信タイトル</label> <input id=’title’ type=’text’ value=’はじめての雑談配信’> </div> <div> <label for=’date’>配信日</label> <input id=’date’ type=’date’> </div> <div> <label for=’start’>開始時刻</label> <input id=’start’ type=’time’ value=’21:00’> </div> <div> <label for=’end’>終了時刻</label> <input id=’end’ type=’time’ value=’22:30’> </div> </div>
<div class=’buttons’> <button id=’save’ class=’primary’ type=’button’>PNG 画像を保存する</button> <button id=’sample’ class=’ghost’ type=’button’>見本の内容に戻す</button> </div> <p id=’message’ class=’message’>PNG 画像を保存するを押すと、ファイルがダウンロードされます。</p>
<div class=’preview’> <canvas id=’board’ width=’1920’ height=’1080’></canvas> </div> </div>
<script> // 画面の大きさと余白。ここを決めてから中身を置いていきます。 const WIDTH = 1920; const HEIGHT = 1080; const PAD = 140;
const COLOR = { background: ’#12161F’, value: ’#F2F6FB’, label: ’#8FA3BF’, rule: ’#2A3346’, accent: ’#E8B93B’ };
const FONT_JA = `’Hiragino Sans’,’Yu Gothic’,’Noto Sans JP’,’Meiryo’,sans-serif`; const FONT_NUMBER = `’SFMono-Regular’,’Consolas’,’DejaVu Sans Mono’,’Menlo’,monospace`;
const TITLE_SIZES = [96, 84, 72, 64]; const TITLE_LINE_HEIGHT = 1.34; const TITLE_MAX_LINES = 3;
const canvas = document.getElementById(’board’); const ctx = canvas.getContext(’2d’); const titleInput = document.getElementById(’title’); const dateInput = document.getElementById(’date’); const startInput = document.getElementById(’start’); const endInput = document.getElementById(’end’); const message = document.getElementById(’message’);
// 配信日の入力(2026-09-21)を、画像に出す言葉に直します。 function dateToText(value){ const parts = value.split(’-’); const year = Number(parts[0]); const month = Number(parts[1]); const day = Number(parts[2]); const weekday = [’日’,’月’,’火’,’水’,’木’,’金’,’土’][new Date(year, month - 1, day).getDay()]; return year + ’年’ + month + ’月’ + day + ’日(’ + weekday + ’)’; }
function todayValue(){ const now = new Date(); const month = String(now.getMonth() + 1).padStart(2, ’0’); const day = String(now.getDate()).padStart(2, ’0’); return now.getFullYear() + ’-’ + month + ’-’ + day; }
// 日本語は語の切れ目が無いので、一文字ずつ測って折り返します。 function wrapText(text, maxWidth){ const lines = []; let line = ’’; for (const char of text){ const next = line + char; if (line !== ’’ && ctx.measureText(next).width > maxWidth){ lines.push(line); line = char === ’ ’ ? ’’ : char; } else { line = next; } } lines.push(line); return lines; }
function drawBoard(data){ // 背景は透かさず塗ります。明るい映像の上でも暗い映像の上でも、 // 文字の下に必ずこの濃い色が来るようにするためです。 ctx.fillStyle = COLOR.background; ctx.fillRect(0, 0, WIDTH, HEIGHT);
ctx.textBaseline = ’top’;
// 辛子色の細い線。ここだけ色を使います。 ctx.fillStyle = COLOR.accent; ctx.fillRect(PAD, 118, 132, 6);
// 見出しと中身:配信タイトル ctx.fillStyle = COLOR.label; ctx.font = ’700 38px ’ + FONT_JA; ctx.fillText(’配信タイトル:’, PAD, 160);
const maxWidth = WIDTH - PAD * 2; let titleSize = TITLE_SIZES[TITLE_SIZES.length - 1]; let titleLines = null; for (const size of TITLE_SIZES){ ctx.font = ’700 ’ + size + ’px ’ + FONT_JA; const wrapped = wrapText(data.title, maxWidth); if (wrapped.length <= TITLE_MAX_LINES){ titleSize = size; titleLines = wrapped; break; } } if (titleLines === null){ ctx.font = ’700 ’ + titleSize + ’px ’ + FONT_JA; titleLines = wrapText(data.title, maxWidth); } if (titleLines.length > TITLE_MAX_LINES){ titleLines = titleLines.slice(0, TITLE_MAX_LINES); titleLines[TITLE_MAX_LINES - 1] = titleLines[TITLE_MAX_LINES - 1].slice(0, -1) + ’…’; }
ctx.fillStyle = COLOR.value; ctx.font = ’700 ’ + titleSize + ’px ’ + FONT_JA; const titleLineHeight = Math.round(titleSize * TITLE_LINE_HEIGHT); titleLines.forEach(function(line, index){ ctx.fillText(line, PAD, 232 + index * titleLineHeight); });
// 上下を分ける線 ctx.fillStyle = COLOR.rule; ctx.fillRect(PAD, 760, WIDTH - PAD * 2, 2);
// 下の段:配信日・開始時刻・終了時刻 const columns = [ { label: ’配信日:’, text: data.date, font: FONT_JA }, { label: ’開始時刻:’, text: data.start, font: FONT_NUMBER }, { label: ’終了時刻:’, text: data.end, font: FONT_NUMBER } ];
const valueSize = 64; const widths = []; let totalWidth = 0; columns.forEach(function(column){ ctx.font = ’700 ’ + valueSize + ’px ’ + column.font; const width = ctx.measureText(column.text).width; widths.push(width); totalWidth += width; });
const gap = Math.max(80, ((WIDTH - PAD * 2) - totalWidth) / (columns.length - 1)); let x = PAD; columns.forEach(function(column, index){ ctx.fillStyle = COLOR.label; ctx.font = ’700 36px ’ + FONT_JA; ctx.fillText(column.label, x, 830);
ctx.fillStyle = COLOR.value; ctx.font = ’700 ’ + valueSize + ’px ’ + column.font; ctx.fillText(column.text, x, 900);
x += widths[index] + gap; }); }
function readInput(){ const title = titleInput.value.trim(); if (title === ’’) return { error: ’配信タイトルを入れてください。’ }; if (dateInput.value === ’’) return { error: ’配信日を入れてください。’ }; if (startInput.value === ’’) return { error: ’開始時刻を入れてください。’ }; if (endInput.value === ’’) return { error: ’終了時刻を入れてください。’ }; return { title: title, date: dateToText(dateInput.value), start: startInput.value.slice(0, 5), end: endInput.value.slice(0, 5) }; }
function render(){ const data = readInput(); if (data.error){ message.textContent = data.error; message.className = ’message alert’; return null; } message.textContent = ’この絵で保存できます。’; message.className = ’message’; drawBoard(data); return data; }
function fillSample(){ titleInput.value = ’はじめての雑談配信’; dateInput.value = todayValue(); startInput.value = ’21:00’; endInput.value = ’22:30’; }
document.getElementById(’sample’).addEventListener(’click’, function(){ fillSample(); render(); });
document.getElementById(’save’).addEventListener(’click’, function(){ if (render() === null) return; const fileName = ’配信ふりかえり_’ + dateInput.value + ’.png’; const link = document.createElement(’a’); link.download = fileName; link.href = canvas.toDataURL(’image/png’); link.click(); message.textContent = fileName + ’ の保存をはじめました。ブラウザのダウンロードをごらんください。’; });
[titleInput, dateInput, startInput, endInput].forEach(function(input){ input.addEventListener(’input’, render); });
fillSample();
render();
</script>
</body>
</html>
写し終えたら、お使いのブラウザでそのファイルを開いてみてください。入力欄と、下に黒い絵が出れば、そこまでは来ています。
動かす前に、お手元で用意するもの
- パソコン。Windows でも macOS でも大丈夫です。絵を描くのに、ブラウザがはじめから持っている canvas という仕組みを使っています。
- 文字を書くための道具(テキストエディタ)。お使いのものにはじめから入っているものでも構いません。
- 日本語のフォント。ふつうは、はじめから入っています。Windows なら游ゴシックかメイリオ、macOS ならヒラギノ角ゴです。もし入っていなければ、Noto Sans JP という書体を入れてください。配布元は Google の Noto のページ(https://fonts.google.com/noto)です。
- 配信の中で出したいときは、OBS Studio。公式の配布ページ(https://obsproject.com)から取れます。
手順は、次の三つだけです。
- 上のコードをテキストエディタに貼って、furikaeri.html という名前で保存します。保存のときに、文字の種類は UTF-8 を選んでください。
- 保存したファイルをブラウザで開きます。ダブルクリックで開くのがいちばん簡単です。
- 入力欄に四つを入れて、「PNG 画像を保存する」を押します。
ファイルの置き場所は、わかりやすいところで構いません。デスクトップでも、書類のフォルダの中でも。OBS のブラウザソースには、お手元のファイルを指定する欄があります。
コードの中のフォントの指定は、ヒラギノ角ゴ → 游ゴシック → Noto Sans JP → メイリオ、の順に並べています。お使いの環境にあるものが、上のものから順に使われます。ですから、全部をそろえる必要はありません。
お借りしたものと、その出どころ
先に線引きを書きます。このファイルの中の HTML・CSS・JavaScript は、全部わたくし(辛子屋 芥子)が書きました。画像を作るためのライブラリは、別に読み込んでいません。ブラウザがはじめから持っている仕組みだけで描いています。
お借りしたのは、書体(フォント)です。
- Noto Sans JP — Google が作った書体です。SIL Open Font License 1.1 という形で配られています。配布元は Google Fonts(https://fonts.google.com/noto)です。
- ヒラギノ角ゴ(Hiragino Sans)— Apple の書体で、macOS に付属しています。
- 游ゴシック(Yu Gothic)/メイリオ(Meiryo)— Microsoft の書体で、Windows に付属しています。
- OBS Studio — OBS Project が作っている配信ソフトです。配布元は https://obsproject.com、ソースは GitHub(https://github.com/obsproject/obs-studio)にあります。
上の三つの書体は、お手元のパソコンに元から入っているものを、そのまま使わせていただく形です。この記事のために別に取ってくる必要はありません。名前に「角ゴ」「ゴシック」と付くものが、日本語の書体です。
OBS のブラウザソースで動かしてみた画面
OBS の「ソース」から「ブラウザ」を選んで、さきほどのファイルを指定します。その設定の途中の画面が、こちらです。

幅と高さは、出来上がる絵と同じ 1920 × 1080 にしています。そうすると、絵の縁と配信の枠が重なって、余白が出ません。
そして、実際に OBS の画面に出たところです。

HTML の要素は 30 個で、閉じ忘れはありませんでした。表示の確認は、このブラウザソースに読み込んで行いました。
確かめたのはここまでです。わたくしは V としての活動予定が未定で、配信をしておりませんので、「実際の配信で出してみたらどうだったか」は書けません。そこは、読む方の手元で試していただくことになります。入力と保存の操作は、お使いのブラウザでファイルを開いて試すのが、いちばん確かだと思います。
こういう使い方はいかがですか
- 配信のあとの一枚として、そのままポストする。
- アーカイブのサムネイルにする。タイトルと日付と時間が入っているので、並んだときに見分けが付きます。
- 配信の終わりに、ブラウザソースを映して出す。お別れのあいさつの横に置く形です。
どれも、うまくいくかは分かりません。「こういう場面がありそうだ」という話です。読む方のほうが、もっとよい使い方をご存じかもしれません。
こう広げられます:手を入れる所
最後に、どこを触ると何が変わるかを書いておきます。すべて、さきほどのコードの中にあります。
- 絵の寸法:
const WIDTH = 1920;とconst HEIGHT = 1080;の二行です。縦の配信にしたいときは、1080 と 1920 を入れかえます。ただし、そのときは下の段の三つの並びや、字の位置も見直しが要ります。横に並べたままでは、はみ出します。 - 左右の余白:
const PAD = 140;です。小さくすると、その分だけ字が横に広がります。 - 色:
const COLOR = { ... }の中の五つです。background(背景)、value(中身の字)、label(見出しの字)、rule(区切りの線)、accent(上に一本入る辛子色の線)。accent を変えると、全体の印象が一番大きく動きます。 - タイトルの字の大きさ:
const TITLE_SIZES = [96, 84, 72, 64];です。長い題のときに、この順に小さくしていきます。段を増やせば、もっと長い題にも付いていけます。 - タイトルの行間:
const TITLE_LINE_HEIGHT = 1.34;です。大きくすると、行と行が離れます。 - タイトルを切る行数:
const TITLE_MAX_LINES = 3;です。増やすと、長い題でも切らずに出せますが、その分だけ下の段に近づきます。 - 下の段の字の大きさ:
const valueSize = 64;です。 - 下の段の項目:
const columns = [ ... ];の配列です。三つ並んでいる所を増やすと、その分の幅を測り直して、間を詰めながら並べます。四つ以上にするときは、幅が足りるかを先に見てください。 - 字の位置:118(上の辛子色の線の高さ)、160(「配信タイトル:」の見出し)、232(タイトルのはじめ)、760(上下を分ける線)、830(下の段の見出し)、900(下の段の中身)。数字を一つ動かすと、その行だけが上下に動きます。
- 書体:
FONT_JAとFONT_NUMBERの二つです。数字は、等幅の書体にしてあります。時刻が並んでも桁がそろうので、見比べやすくなります。
大きく作りかえるというより、余白と字の大きさを少しずつ動かして、ご自分の配信に合う形を見つけるのが、いちばん早いと思います。
決まった形の一枚が、決まった手順で出せる。それだけの道具です。お手元で、少しずつ変えてみてください。


コメント