サイトにアクセスするたびに違うキャッチコピーやバナーを表示したい、お知らせをランダムに出し分けたい、というケースは意外と多くあります。Math.random() を使えば数行で実装できますが、「同じ内容が何度も連続する」「特定のバナーだけ出やすくしたい」「今日は1回しか表示したくない」といった実務要件に応えるには少し工夫が必要です。
この記事では基本実装から重み付き確率・シャッフルキュー・localStorage を使った日次制御まで、ランダム表示に関する実装パターンを体系的に解説します。
基本実装:配列からランダムに1つ表示する
Math.random() と Math.floor() を組み合わせて配列のランダムなインデックスを選ぶのが基本パターンです。
const messages = [
'いらっしゃいませ!今日もよろしくお願いします。',
'本日のおすすめをチェックしてみてください。',
'ご来店ありがとうございます。',
];
// 0 〜 messages.length-1 のランダムなインデックスを選ぶ
const index = Math.floor(Math.random() * messages.length);
document.getElementById('welcome-msg').textContent = messages[index];
Math.random() は 0 以上 1 未満 の小数を返します。Math.random() * n で 0 以上 n 未満、Math.floor() で切り捨てると 0 〜 n-1 の整数になります。乱数の詳しい使い方はJavaScriptで乱数を生成する方法も参照してください。HTMLブロック(画像・リンク・複雑な構造)をランダムに切り替える場合は innerHTML を使います。
<div id="random-banner"></div>
const banners = [
'<img src="/img/banner-a.jpg" alt="春のキャンペーン">',
'<img src="/img/banner-b.jpg" alt="新商品のご案内">',
'<img src="/img/banner-c.jpg" alt="会員登録はこちら">',
];
const container = document.getElementById('random-banner');
container.innerHTML = banners[Math.floor(Math.random() * banners.length)];
innerHTML は HTML として解析されるため、外部から取得した文字列を直接代入するとXSSの原因になります。固定文字列を表示するだけなら textContent を使い、HTMLが必要な場合は内容を自分でコントロールできる静的配列のみにしてください。HTMLに書いた複数要素をランダムに1つだけ表示する
JavaScriptでHTMLを生成するのではなく、すべての候補をHTMLに書いておき、1つだけを表示する方法です。CMSで管理しやすく、SEO的にも有利なケースがあります。
<div class="random-group"> <div class="random-item">コンテンツA:春のキャンペーン</div> <div class="random-item">コンテンツB:新商品のご案内</div> <div class="random-item">コンテンツC:会員登録のススメ</div> </div>
.random-item {
display: none;
}
const items = document.querySelectorAll('.random-group .random-item');
if (items.length > 0) {
const pick = Math.floor(Math.random() * items.length);
items[pick].style.display = 'block';
}
- HTMLエディタ(WordPressなど)でコンテンツを管理できる
- JavaScriptが無効でも全候補が表示される(フォールバックとして機能)
- コンテンツの追加・削除がHTMLだけで完結する
重み付きランダム:出現確率を個別に設定する
「バナーAを50%、バナーBを30%、バナーCを20%の確率で表示したい」という場合、単純な Math.random() では均等になってしまいます。重み付きランダムで確率をコントロールします。
/**
* 重み付きランダムで1つを選ぶ
* @param {Array<{content: string, weight: number}>} items
* @returns {string} 選ばれたコンテンツ
*/
function weightedRandom(items) {
// 重みの合計を計算
const totalWeight = items.reduce((sum, item) => sum + item.weight, 0);
let rand = Math.random() * totalWeight;
for (const item of items) {
rand -= item.weight;
if (rand <= 0) return item.content;
}
// 浮動小数点の誤差対策:最後のアイテムを返す
return items[items.length - 1].content;
}
// 使用例:weight の合計は任意(割合で考えると分かりやすい)
const banners = [
{ content: '<img src="/img/banner-a.jpg" alt="キャンペーンA">', weight: 50 }, // 50%
{ content: '<img src="/img/banner-b.jpg" alt="キャンペーンB">', weight: 30 }, // 30%
{ content: '<img src="/img/banner-c.jpg" alt="キャンペーンC">', weight: 20 }, // 20%
];
document.getElementById('banner').innerHTML = weightedRandom(banners);
重みの合計(100)に対して
Math.random() * 100 で 0〜100 の乱数を得て、先頭から重みを減算していきます。最初に 0 以下になったアイテムが選ばれます。重みが大きいほど選ばれる確率が高くなります。シャッフルキュー:同じコンテンツが連続しないようにする
単純なランダムでは同じコンテンツが連続することがあります。「全コンテンツを一巡したら次のラウンドに進む」シャッフルキュー方式で、均等に全コンテンツを表示できます。
/**
* Fisher-Yates シャッフル(配列を破壊的にシャッフル)
*/
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
/**
* シャッフルキュー:全アイテムを一巡したら次のラウンドを作る
*/
class ShuffleQueue {
#items;
#queue = [];
constructor(items) {
this.#items = [...items];
this.#refill();
}
#refill() {
this.#queue = shuffle([...this.#items]);
}
/** 次のアイテムを取り出す */
next() {
if (this.#queue.length === 0) this.#refill();
return this.#queue.pop();
}
}
// 使用例:ページを読み込むたびにキューの先頭を表示
const messages = [
'キャッチコピーA',
'キャッチコピーB',
'キャッチコピーC',
'キャッチコピーD',
];
const queue = new ShuffleQueue(messages);
document.getElementById('headline').textContent = queue.next();
[...arr].sort(() => Math.random() - 0.5) は一見シャッフルに見えますが、sort関数の比較結果が不定になるため、配列の後方要素が選ばれにくくなる偏りが生じます。Fisher-Yates アルゴリズムは各要素が等確率で選ばれることが数学的に保証された正しいシャッフルです。localStorage でコンテンツの表示回数・日次を制御する
「1日に1回だけポップアップを表示する」「今日見たコンテンツは明日に回す」など、localStorage を使ってランダム表示を賢く制御できます。
const STORAGE_KEY = 'shown-content-today';
/**
* 今日の日付文字列を返す(例: "2026-04-06")
*/
function todayStr() {
return new Date().toISOString().slice(0, 10);
}
/**
* 今日表示済みのコンテンツIDのSetを返す
*/
function getShownToday() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
// 日付が変わっていたらリセット
if (saved.date !== todayStr()) return new Set();
return new Set(saved.ids || []);
} catch {
return new Set();
}
}
/**
* コンテンツIDを「今日表示済み」として記録する
*/
function markShown(id) {
try {
const shown = getShownToday();
shown.add(id);
localStorage.setItem(STORAGE_KEY, JSON.stringify({
date: todayStr(),
ids: [...shown],
}));
} catch {
/* プライベートモード・容量超過でも止まらない */
}
}
// 使用例:今日未表示のコンテンツからランダムに選ぶ
const contents = [
{ id: 'a', text: 'お知らせA' },
{ id: 'b', text: 'お知らせB' },
{ id: 'c', text: 'お知らせC' },
];
const shown = getShownToday();
const unseen = contents.filter(c => !shown.has(c.id));
if (unseen.length > 0) {
const pick = unseen[Math.floor(Math.random() * unseen.length)];
document.getElementById('notice').textContent = pick.text;
markShown(pick.id);
} else {
// 全コンテンツを今日見た → 全体からランダムに選ぶ
const pick = contents[Math.floor(Math.random() * contents.length)];
document.getElementById('notice').textContent = pick.text;
}
const POPUP_KEY = 'popup-shown-date';
function shouldShowPopup() {
try {
return localStorage.getItem(POPUP_KEY) !== todayStr();
} catch {
return true; // localStorage が使えない場合は表示する
}
}
function recordPopupShown() {
try {
localStorage.setItem(POPUP_KEY, todayStr());
} catch { /* ignore */ }
}
// 今日まだ表示していなければ表示
if (shouldShowPopup()) {
document.getElementById('popup').hidden = false;
recordPopupShown();
}
完成形:RandomContent クラス
これまでの実装パターンをひとまとめにした汎用クラスです。モードを切り替えるだけで純粋ランダム・重み付き・シャッフルキューを使い分けられます。
class RandomContent {
#items;
#mode;
#queue = [];
/**
* @param {Array} items - コンテンツ配列
* 純粋ランダム / シャッフル: string[]
* 重み付き: { content: string, weight: number }[]
* @param {'pure' | 'weighted' | 'shuffle'} mode
*/
constructor(items, mode = 'pure') {
this.#items = items;
this.#mode = mode;
if (mode === 'shuffle') this.#refillQueue();
}
/** 次のコンテンツを返す */
next() {
switch (this.#mode) {
case 'weighted': return this.#weightedPick();
case 'shuffle': return this.#shufflePick();
default: return this.#purePick();
}
}
/** ターゲット要素に表示する */
render(selector) {
const el = typeof selector === 'string'
? document.querySelector(selector)
: selector;
if (!el) return;
const content = this.next();
// string なら innerHTML、オブジェクトなら content プロパティを使う
const html = typeof content === 'string' ? content : content.content;
el.innerHTML = html;
}
#purePick() {
return this.#items[Math.floor(Math.random() * this.#items.length)];
}
#weightedPick() {
const total = this.#items.reduce((s, i) => s + i.weight, 0);
let r = Math.random() * total;
for (const item of this.#items) {
r -= item.weight;
if (r <= 0) return item;
}
return this.#items[this.#items.length - 1];
}
#shufflePick() {
if (this.#queue.length === 0) this.#refillQueue();
return this.#queue.pop();
}
#refillQueue() {
this.#queue = [...this.#items];
for (let i = this.#queue.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.#queue[i], this.#queue[j]] = [this.#queue[j], this.#queue[i]];
}
}
}
// ① 純粋ランダム
const greet = new RandomContent([
'おはようございます!',
'こんにちは!',
'こんばんは!',
], 'pure');
greet.render('#greeting');
// ② 重み付き
const banner = new RandomContent([
{ content: '<img src="/img/a.jpg" alt="A">', weight: 60 },
{ content: '<img src="/img/b.jpg" alt="B">', weight: 40 },
], 'weighted');
banner.render('#banner');
// ③ シャッフルキュー(均等に全件表示)
const tips = new RandomContent([
'Tip 1: コードを書く前に設計する',
'Tip 2: 変数名は具体的に',
'Tip 3: 関数は1つのことだけする',
], 'shuffle');
tips.render('#daily-tip');
よくある質問(FAQ)
Math.random() は数学的に均等なので、同じコンテンツが続くことは確率的にあり得ます。気になる場合はシャッフルキュー方式に切り替えてください。全コンテンツを一巡してから次のラウンドに進むため、連続する可能性がほぼなくなります。sessionStorage を使います。localStorage と同じAPIですが、タブを閉じると自動的に消えます。ページ読み込み時に sessionStorage を確認し、値があればそれを表示、なければランダムに選んで保存する流れで実装できます。useState の初期値に () => weightedRandom(items) のように関数を渡すと、初回レンダリング時だけ実行されます。Vueなら data() か setup() の中で ref(pickRandom(items)) とすれば同様です。コンポーネントの外側で実行するとSSR時にサーバーとクライアントで値がずれる場合があるため注意してください。localStorage に表示カウントを保存できますが、デバイスをまたいだ集計はできません。正確な計測にはサーバーへの記録か Google Analytics などの計測タグに表示イベントを送信する方法が現実的です。まとめ
| 要件 | 実装パターン |
|---|---|
| 単純にランダムで1つ表示 | Math.floor(Math.random() * array.length) |
| 特定コンテンツを出やすくしたい | 重み付きランダム(weight プロパティ) |
| 同じコンテンツが続かないようにしたい | Fisher-Yates シャッフル + キュー管理 |
| 1日1回だけ表示したい | localStorage に日付を記録して制御 |
| 同じセッション中は固定したい | sessionStorage に選択結果を保存 |
JavaScriptの乱数生成の詳細はJavaScriptで乱数を生成する方法、localStorageの使い方はlocalStorage完全ガイドもあわせて参照してください。