GODRICH

【コピペOK】信頼を作るLPパーツ9選

LPパーツは、統計カウンターやロゴバーから比較表・問い合わせフォームまで、ランディングページのあちこちに散らばっていた信頼づくりの部品をひとまとめにしたコード集です。LPにそのまま貼れる九つのうち一つは、ページが開いた瞬間に数字が実際に0から目標値まで上がります。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

九つは知名度順ではなく、訪問者の信頼が積み上がっていく順番に並べました。まず数字で規模を見せ(主要指標カウンター)、すでに使っている会社の名前を流し(導入企業ロゴバー)、料金の違いを表で見せ(機能比較表)、実際に働く人を見せ(チームメンバーグリッド)、次の情報を受け取ってもらい(ニュースレター登録バンド)、つながるサービスを並べ(連携サービスグリッド)、これまでの道とこれからの道を見せ(ロードマップタイムライン)、第三者の評価を根拠として示し(受賞・メディア掲載)、最後に実際の問い合わせを受け取ります(お問い合わせフォーム)。ロゴ・アバター・媒体名はすべてこのサイト共通のインク・ブルー・クリームのトークンだけで組んだ図形で、実際のロゴファイルに差し替えてもグリッドと配置はそのまま保たれます。

01主要指標カウンター

三つの数字はページが開いた瞬間、requestAnimationFrameで実際に0から目標値までカウントアップします。アイコンは常に静かに上下へ揺れ、行全体が動いている印象を保ちます。

requestAnimationFramecountertransform
document.querySelectorAll('.stats__n').forEach(function (el, i) {
  var target = Number(el.getAttribute('data-target'));
  var DUR = 1100, start = null, delay = i * 140;
  function frame(ts) {
    if (!start) start = ts;
    var t = Math.max(0, ts - start - delay);
    var p = Math.min(1, t / DUR);
    var eased = 1 - Math.pow(1 - p, 3);
    el.textContent = Math.round(eased * target).toLocaleString('ja-JP');
    if (p < 1) requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
});

02導入企業ロゴバー

ロゴの列は切れ目なく流れ続け、ボタンを押すとaria-pressedが切り替わって実際に止まり、もう一度押すとまた流れます。トラックは同じ六つのロゴをもう一度つなげただけなので、つなぎ目が見えません。

translateX(-50%)margin-rightaria-pressed
.lc__track {
  display: flex;
  width: max-content;
  animation: lc-scroll 9s linear infinite;
}
.lc__track li {
  flex: 0 0 auto;
  margin-right: 24px; // gap の代わりに margin-right
}
@keyframes lc-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

03機能比較表

プラン名をクリックするとaria-pressedが切り替わり、その列のチェック・バツのアイコンが実際に拡大して強調されます。二つの列は1秒ごとに自動でも交互に強調されるので、押さなくても違いを目で追えます。

aria-pressedscaleclick
function select(plan) {
  document.querySelectorAll('.fc__plan').forEach(function (b) {
    var on = b.getAttribute('data-plan') === plan;
    b.classList.toggle('is-on', on);
    b.setAttribute('aria-pressed', String(on));
  });
  document.querySelectorAll('.fc__cell').forEach(function (c) {
    c.classList.toggle('is-on', c.getAttribute('data-plan') === plan);
  });
}

04チームメンバーグリッド

メンバーカードをクリックするか Enter を押すとaria-expandedが切り替わり、一行紹介がgrid-template-rowsで実際に開きます。開いているカードのアバターは少し拡大して傾き、どのメンバーが開いているかが一目で分かります。

aria-expandedgrid-template-rowsclick
document.querySelectorAll('.tm__head').forEach(function (btn) {
  btn.addEventListener('click', function () {
    var card = btn.closest('.tm__card');
    var open = btn.getAttribute('aria-expanded') === 'true';
    btn.setAttribute('aria-expanded', String(!open));
    card.classList.toggle('is-open', !open);
  });
});

05ニュースレター登録バンド

メールアドレスを入力して登録すると正規表現で実際に形式を検査し、通過するとstroke-dashoffsetでチェックが実際に描かれ、ボタンの文字が「登録完了」に変わります。形式が違うと入力欄が左右に短く揺れ、どこが間違っているかがすぐ分かります。

정규식stroke-dashoffsetsubmit
var RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
form.addEventListener('submit', function (e) {
  e.preventDefault();
  if (RE.test(input.value)) {
    form.classList.add('is-done');
    msg.textContent = input.value + ' 宛に確認メールを送りました。';
  } else {
    input.classList.add('is-shake');
    msg.textContent = 'メールの形式を確認してください。';
  }
});

06連携サービスグリッド

接続ボタンを押すとaria-pressedが切り替わり、丸い点が実際に右へスライドして文字が「連携済み」に変わります。点は自分専用のトラックの中だけで動くので、隣の文字と重なりません。

aria-pressedtranslateXclick
document.querySelectorAll('.ig__toggle').forEach(function (btn) {
  btn.addEventListener('click', function () {
    var on = btn.getAttribute('aria-pressed') === 'true';
    btn.setAttribute('aria-pressed', String(!on));
    btn.querySelector('.ig__state').textContent = on ? '連携' : '連携済み';
  });
});

07ロードマップタイムライン

スポットライトは自動で次の四半期へ移り、項目をクリックするか Tab でフォーカスを移すと、実測した位置までtranslateYで実際に移動します。完了した四半期と予定の四半期は別のアイコンで区別されます。

setIntervalgetBoundingClientRecttranslateY
function moveTo(idx) {
  i = idx;
  var top = steps[idx].getBoundingClientRect().top - list.getBoundingClientRect().top;
  spot.style.transform = 'translateY(' + top + 'px)';
  steps.forEach(function (s, n) { s.classList.toggle('is-active', n === idx); });
}

08受賞・メディア掲載

媒体名をクリックするとaria-expandedが切り替わり、その媒体の引用文が実際に開き、一度に一つだけ開きます。媒体は自動でも一つずつ順番に開くので、何も押さなくても引用文をすべて見られます。

aria-expandedgrid-template-rowsclick
function openOnly(idx) {
  btns.forEach(function (b, n) {
    var on = n === idx;
    b.setAttribute('aria-expanded', String(on));
    b.closest('.ap__badge').classList.toggle('is-open', on);
  });
}

09分割型お問い合わせフォーム

名前・メール・お問い合わせ内容をすべて入力して送信すると、紙飛行機のアイコンが実際に飛んで消え、ボタンの文字が「送信完了」に変わります。空欄があるか、メールの形式が違うと、その欄だけが短く揺れます。

정규식translate+opacitysubmit
inputs.forEach(function (inp) {
  if (!inp.value.trim() || (inp.type === 'email' && !RE.test(inp.value))) {
    ok = false;
    inp.classList.add('is-shake');
  }
});
if (ok) {
  form.classList.add('is-sent');
  msg.textContent = 'お問い合わせを受け付けました。1営業日以内にご連絡します。';
}

どこで壊れるか — 落とし穴

九つを作る間に、実際に三回つまずきました。一つ目は導入企業ロゴバー(02)です。六つのロゴを二回つなげてtranslateX(-50%)で無限に流していたのですが、最初は項目の間隔をgapで指定していました。ロゴが十二個あると間隔は十一個しかなく、-50%の地点が間隔の半分だけ足りずに、一周するたびにロゴの列が少しだけ跳ねていました。gapをやめて項目ごとにmargin-rightを指定すると、つなぎ目が完全に消えました。

二つ目は幅320px・高さ200pxの画面確認で見つかりました。チームメンバーグリッド(04)とお問い合わせフォーム(09)は最初、狭い画面で縦に積む(flex-direction: column)ようにしていたのですが、むしろ縦に長くなって実測値が画面の高さを超えてしまいました。積むのをやめて文字サイズと余白だけを縮めると、両方とも画面に収まりました。

三つ目は連携サービスグリッド(06)の接続トグルです。丸い点を「連携」「連携済み」の文字と同じ行に並べ、transform: translateXだけで動かしていたところ、オンになった瞬間に点が文字の上に実際に重なっていました。点を専用の小さなトラックに入れ、トラックごと配置し直すと重なりが消えました。

アクセシビリティ

九つ全部、zip をrghhpe4vで開いて受け取るコードそのままで、prefers-reduced-motion: reduceでは装飾アニメーションだけを止め、状態自体は残します。主要指標カウンター(01)はアイコンの揺れだけを止め、数字のカウントアップは情報なのでそのまま残し、導入企業ロゴバー(02)の流れも止まりますが一時停止ボタンはそのまま動きます。機能比較表(03)と連携サービスグリッド(06)の状態はaria-pressedで、チームメンバーグリッド(04)と受賞・メディア掲載(08)の開閉状態はaria-expandedで伝えます。ニュースレター登録バンド(05)とお問い合わせフォーム(09)の結果文言はrole="status"なので、スクリーンリーダーが成功・失敗をすぐ読み上げます。開閉アニメーションに使ったgrid-template-rows: 0fr → 1frの遷移はMDNのgrid-template-rowsのページにある標準のトラックサイズ構文をそのまま使い、prefers-reduced-motion自体はMDNの資料に沿っています。

イベント・プレスルームのセクションが気になる方はテンプレート一覧もあわせてご覧ください。このサイトを作っている人の情報はサイト紹介ページにあります。

FAQ

導入企業ロゴバー(02)は実際に自動で流れますか、それとも静止画ですか?

実際に流れます。translateX(-50%)の CSS アニメーションで無限にループし、ロゴの一覧をちょうど一回多くつなげているので、ループの地点につなぎ目が見えません。一時停止ボタンを押すとanimation-play-stateが実際にpausedに変わります。

React 版も CSS 版と同じ動きをしますか?

はい、同じです。開閉・連携・強調といった状態はすべてuseStateで管理し、変数三つ(durationeasingcolor)は props として受け取り、そのまま CSS 変数に渡します。ロードマップ(07)の自動移動とお問い合わせフォーム(09)の自動フォーカスも、useEffectの中で同じロジックを使います。

お問い合わせフォーム(09)は実際にメールをどこかへ送りますか?

いいえ、このデモ自体は形式の検査と送信完了の表示までしか実際には動作せず、本物の送信サーバーはつながっていません。formsubmitイベントを受け取って正規表現で検査する部分までがこのテンプレートの役割で、実際に使うときはその場所に本物の API 呼び出しを足すだけで、検査・表示のロジックはそのまま使い回せます。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。