【JavaScript】アクセスするたびにランダムでコンテンツを表示する方法|重み付き確率・シャッフルキュー・localStorage日次制御まで解説

サイトにアクセスするたびに違うキャッチコピーやバナーを表示したい、お知らせをランダムに出し分けたい、というケースは意外と多くあります。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() の範囲:
Math.random()0 以上 1 未満 の小数を返します。Math.random() * n0 以上 n 未満Math.floor() で切り捨てると 0 〜 n-1 の整数になります。乱数の詳しい使い方はJavaScriptで乱数を生成する方法も参照してください。

HTMLブロック(画像・リンク・複雑な構造)をランダムに切り替える場合は innerHTML を使います。

HTML(切り替え対象の要素)
<div id="random-banner"></div>
HTMLブロックをランダムに差し込む
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 に外部データを入れない:
innerHTML は HTML として解析されるため、外部から取得した文字列を直接代入するとXSSの原因になります。固定文字列を表示するだけなら textContent を使い、HTMLが必要な場合は内容を自分でコントロールできる静的配列のみにしてください。

HTMLに書いた複数要素をランダムに1つだけ表示する

JavaScriptでHTMLを生成するのではなく、すべての候補をHTMLに書いておき、1つだけを表示する方法です。CMSで管理しやすく、SEO的にも有利なケースがあります。

HTML(候補要素をすべて書いておく)
<div class="random-group">
  <div class="random-item">コンテンツA:春のキャンペーン</div>
  <div class="random-item">コンテンツB:新商品のご案内</div>
  <div class="random-item">コンテンツC:会員登録のススメ</div>
</div>
CSS(初期状態は全非表示)
.random-item {
  display: none;
}
ランダムに1つだけ表示する
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 シャッフル + キュー管理
/**
 * 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();
Fisher-Yates シャッフルを使う理由:
[...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;
}
1日1回だけ表示する(ポップアップなど)
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();
}
localStorageの基本的な使い方についてはlocalStorage完全ガイドで詳しく解説しています。プライベートモードで書き込みが失敗するケースも含めたエラーハンドリングも参照してください。

完成形:RandomContent クラス

これまでの実装パターンをひとまとめにした汎用クラスです。モードを切り替えるだけで純粋ランダム・重み付き・シャッフルキューを使い分けられます。

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)

Qページを更新するたびに同じコンテンツが出やすい気がします。
AMath.random() は数学的に均等なので、同じコンテンツが続くことは確率的にあり得ます。気になる場合はシャッフルキュー方式に切り替えてください。全コンテンツを一巡してから次のラウンドに進むため、連続する可能性がほぼなくなります。
Q同じセッション中(タブを閉じるまで)は同じコンテンツを表示したいのですが。
AsessionStorage を使います。localStorage と同じAPIですが、タブを閉じると自動的に消えます。ページ読み込み時に sessionStorage を確認し、値があればそれを表示、なければランダムに選んで保存する流れで実装できます。
QReact や Vue でも同じ方法を使えますか?
A使えます。Reactなら useState の初期値に () => weightedRandom(items) のように関数を渡すと、初回レンダリング時だけ実行されます。Vueなら data()setup() の中で ref(pickRandom(items)) とすれば同様です。コンポーネントの外側で実行するとSSR時にサーバーとクライアントで値がずれる場合があるため注意してください。
QSEO的に問題はありますか?ランダムなコンテンツはクローラーに見えますか?
AGooglebotはある程度JavaScriptを実行しますが、クロールのたびに異なるコンテンツが表示されると評価が安定しません。SEOで評価したいメインコンテンツはランダム表示にせず、バナーやキャッチコピーなどのサブ的な要素に留めるのが安全です。
Qコンテンツの出現回数を記録・集計したいのですが。
Aクライアントサイドで集計するなら localStorage に表示カウントを保存できますが、デバイスをまたいだ集計はできません。正確な計測にはサーバーへの記録か Google Analytics などの計測タグに表示イベントを送信する方法が現実的です。

まとめ

要件 実装パターン
単純にランダムで1つ表示 Math.floor(Math.random() * array.length)
特定コンテンツを出やすくしたい 重み付きランダム(weight プロパティ)
同じコンテンツが続かないようにしたい Fisher-Yates シャッフル + キュー管理
1日1回だけ表示したい localStorage に日付を記録して制御
同じセッション中は固定したい sessionStorage に選択結果を保存

JavaScriptの乱数生成の詳細はJavaScriptで乱数を生成する方法、localStorageの使い方はlocalStorage完全ガイドもあわせて参照してください。