GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

【コピペOK】プロモ LP テンプレート9選

プロモ LP テンプレートは、カウントダウンヒーローから申込フォームまで、キャンペーンのランディングページに必要な構成要素をひとまとめにしたコード集です。LPにそのまま貼れる九つのうち一つは、実際の目標時刻まで毎秒減っていくカウントダウンを収めています。

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

九つは知名度順ではなく、訪問者がプロモーションページを上から読み進める順番に並べました。まず締切の時刻で足を止めさせ(カウントダウンヒーロー)、変化の実感を見せ(ビフォーアフター)、選ぶ理由を一つずつ刻み(特典ハイライト)、すでに参加した人数で背中を押し(ソーシャルプルーフ)、値引き幅を数字で見せ(割引料金)、残り枠の少なさでもう一度急かし(締切バナー)、迷いを払い(プロモFAQ)、実際に登録を受け取り(申込フォーム)、次の案内へつなげます(フッター)。担当者の顔やブランドロゴの場所に置いた図形は実物ではなく、すべてこのサイト共通の$subject-ink・$subject-blue・$subject-creamトークンだけで組んであります。キャンペーン lp 構成をそのまま写した並びなので、上から順に貼るだけでも形になります。割引ではなく作ったもので説得する構成ならプロジェクト紹介のテンプレートがその並びで、値を下げずに信頼を積む側は信頼を作るLPパーツが担います。

01カウントダウンヒーロー

キャンペーン lp 事例としてのヒーロー

ヒーロー上段には見出しとサブ見出し、下段にはカウントダウンと参加CTAが横並びで置かれます。

カウントダウンヒーロー
新しいタブで開く

01 · カウントダウンヒーロー — 計測 累積面積 7.657% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

Date.now()transformaria-pressed

毎秒計算し直される数字

秒の数字は実際の目標時刻まで毎秒計算し直されて短く弾み、CTAボタンは押すたびにaria-pressedが実際に反転してラベルごと切り替わります。

.ph__blob { animation: ph-drift 3.4s ease-in-out infinite; }
@keyframes ph-drift {
  0%, 100% { transform: translate(0, 0) scale(1); opacity: .7; }
  50%      { transform: translate(6px, -8px) scale(1.12); opacity: 1; }
}
.ph__num--s { animation: ph-tick 1s $easing infinite; }
@keyframes ph-tick {
  0%, 55%  { transform: translateY(0) scale(1); }
  78%      { transform: translateY(-3px) scale(1.1); }
  100%     { transform: translateY(0) scale(1); }
}

02ビフォーアフター

二枚のパネルが入れ替わる

ビフォー・アフターのボタンを押すと二枚のパネルが実際にopacityでクロスフェードします。

ビフォーアフター
新しいタブで開く

02 · ビフォーアフター — 計測 累積面積 41.445% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

opacityclickaria-pressed

ハイライトは常に流れる

アフターパネルの上には対角線のハイライトが、パネルの状態に関係なく常にすっと流れ続けます。

// 舞台装飾: どちらのパネルが有効でも常に見えるよう、フレームの上(両パネルの兄弟要素)に置く
.ba__shimmer {
  position: absolute;
  inset: -20% -60%;
  z-index: 2;
  background: linear-gradient(100deg, transparent 42%, rgba(255, 247, 230, .42) 50%, transparent 58%);
  animation: ba-shimmer 2.4s linear infinite;
}
@keyframes ba-shimmer {
  0%   { transform: translateX(-30%); opacity: 0; }
  60%  { opacity: .9; }
  100% { transform: translateX(30%); opacity: 0; }
}

03特典ハイライト

1.6秒ごとに照らし替える

特典3行のうち1行を1.6秒ごとにスポットライトが自動で照らし替えます。

特典ハイライト
新しいタブで開く

03 · 特典ハイライト — 計測 累積面積 9.529% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

translateYsetIntervalgetBoundingClientRect

クリックでその位置へ

行をクリックするか、行にフォーカスしてEnterキーを押すと、その位置まで実測した高さぶんすぐに移動します。

function go(i) {
  current = i;
  rows.forEach(function (r, idx) {
    var on = idx === i;
    r.classList.toggle('is-active', on);
    r.setAttribute('aria-pressed', String(on));
  });
  // 実際のレンダー高さを測る — 固定値を決め打ちするとレスポンシブでスポットライトがずれる
  var offset = rows[i].getBoundingClientRect().top - rows[0].getBoundingClientRect().top;
  spot.style.transform = 'translateY(' + offset + 'px)';
}

04ソーシャルプルーフ

4つが控えめに脈打つ

重なったアバター3つと「+N」バッジを合わせた4つが控えめに脈打ちます。

ソーシャルプルーフ
新しいタブで開く

04 · ソーシャルプルーフ — 計測 累積面積 5.21% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

requestAnimationFramecountertransform

0から目標値まで上がる

大きな数字はページが開いた瞬間に0から実際の目標値(2,431)まで上がります。

var target = Number(el.getAttribute('data-target'));
var start = null;
var DUR = 1200;
function frame(ts) {
  if (!start) start = ts;
  var p = Math.min(1, (ts - start) / DUR);
  var eased = 1 - Math.pow(1 - p, 3);
  el.textContent = Math.round(eased * target).toLocaleString('ko-KR');
  if (p < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

05割引料金

スイッチが実際に切り替わる

年間払いのスイッチを押すとaria-checkedが実際に切り替わり、月額・年額の価格パネルがクロスフェードします。

割引料金
新しいタブで開く

05 · 割引料金 — 計測 累積面積 0.458% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

role=switchopacityclick

取り消し線はそのまま

元の価格には取り消し線がそのまま残ります。

sw.addEventListener('click', function () {
  var on = sw.getAttribute('aria-checked') === 'true';
  sw.setAttribute('aria-checked', String(!on));
  monthly.classList.toggle('is-active', on);
  yearly.classList.toggle('is-active', !on);
});

06締切バナー

進捗バーは揺れ続ける

締切の進捗バーは常に控えめに揺れ続けます。

締切バナー
新しいタブで開く

06 · 締切バナー — 計測 累積面積 0.192% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scaleXclickaria-pressed

押すと案内文が長くなる

詳細を表示するボタンを押すとaria-pressedが実際に切り替わり、案内文が本当に長くなります。

// 実際の動作: ボタンを押すとaria-pressedが切り替わり、案内文が実際に伸びる
var btn = document.querySelector('.ub__more');
var msg = document.querySelector('.ub__msg');
var base = msg.textContent;
btn.addEventListener('click', function () {
  var on = btn.getAttribute('aria-pressed') === 'true';
  btn.setAttribute('aria-pressed', String(!on));
  msg.innerHTML = on ? base : base + ' · 마감 후 정가 복귀';
});

07プロモFAQ

開くのは一つだけ

質問をクリックするとその項目だけが実際に開き、ほかは閉じます(単一オープン)。

プロモFAQ
新しいタブで開く

07 · プロモFAQ — 計測 累積面積 36.616% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-rowsclickaria-expanded

閉じた矢印も揺れる

閉じた項目の矢印は、開閉の状態に関係なく常に控えめに揺れ続けます。案内をスクロールに載せて場面のように送りたいなら、スクロールテリングのセクションがその演出を引き受けます。

items.forEach(function (item) {
  var q = item.querySelector('.pf__q');
  q.addEventListener('click', function () {
    var willOpen = !item.classList.contains('is-open');
    items.forEach(function (i) {
      i.classList.remove('is-open');
      i.querySelector('.pf__q').setAttribute('aria-expanded', 'false');
    });
    if (willOpen) { item.classList.add('is-open'); q.setAttribute('aria-expanded', 'true'); }
  });
});

08申込フォーム

正規表現で形式を検証する

メールアドレスを入れて申込ボタンを押すと、正規表現で実際に形式を検証します。

申込フォーム
新しいタブで開く

08 · 申込フォーム — 計測 累積面積 1.704% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

stroke-dashoffset정규식submit

通過と失敗の合図

通過するとSVGチェックがstroke-dashoffsetで描かれ、失敗すると入力欄が左右に短く揺れます。

.sf__btn {
  position: relative;
  isolation: isolate;                    // z-index:-1のグローをこのコンテキストに閉じ込める
}
// 舞台装飾: ボタンの外へにじむグローリング。最初は overflow: hidden を与えたところ、
// 不透明な背景と同じ箱の中でグローがそのまま隠れて測定値が0だった。overflowを外して外へにじませた。
.sf__btn::before {
  content: "";
  position: absolute;
  inset: -10px;
  z-index: -1;
  background: radial-gradient(circle, rgba(47, 109, 246, .55), transparent 70%);
  animation: sf-glow 1.8s ease-in-out infinite;
}
@keyframes sf-glow {
  0%, 100% { opacity: .35; transform: scale(1); }
  50%      { opacity: 1; transform: scale(1.25); }
}

09プロモフッター

一覧が折りたたまれる

ショートカットの開閉ボタンを押すとリンク一覧が実際に折りたたまれたり開いたりします。

フッター
新しいタブで開く

09 · フッター — 計測 累積面積 0.556% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

grid-template-rowsclickaria-expanded

上部のハイライト線

上部の細いハイライト線は、開閉の状態に関係なく常に左右へすっと流れ続けます。

var toggle = document.querySelector('.pt__group-toggle');
var links = document.querySelector('.pt__links-wrap');
toggle.addEventListener('click', function () {
  var open = toggle.getAttribute('aria-expanded') === 'true';
  toggle.setAttribute('aria-expanded', String(!open));
  links.classList.toggle('is-open', !open);
});

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

グローがボタンの下に隠れた

九つを仕上げる途中、実際に同じ系統の不具合へ三回引っかかりました。ひとつ目は申込フォーム(08)です。ボタンの外へにじむグローリングにoverflow: hiddenを与えたところ、不透明な青いボタン背景の下にグローがそのまま隠れ、画面は普通に見えるのに実測した変化はゼロでした。overflowを外してisolation: isolateで重なりの範囲だけを区切ると、リングが表に出てきました。

ハイライトがパネルごと消えた

ふたつ目はビフォーアフター(02)です。最初、対角線のハイライトをアフターパネルの内側に置いていましたが、そのパネルは初期状態でopacity: 0なので、ハイライトごと画面から消えていました。パネルの兄弟要素として外に出し、フレームの一番上に常時描かれるようにして解決しました。

transform を二つで取り合った

みっつ目は特典ハイライト(03)で、性質の違う不具合に出会いました。スポットライトの位置を動かすtranslateYと、装飾用パルスのscaleXを同じ要素のtransformに同居させたところ、パルスの@keyframesが周期ごとにtransform全体を書き直し、位置が0へ巻き戻っていました。位置用の外側要素と、パルス用の内側の子要素へ二重に分けたところ直りました。計測時はいつも先頭の行が選ばれた状態だったため偶然表面化しませんでしたが、実際に別の行をクリックした瞬間に見えていたはずの不具合です。zipの中身は、この三つを直した後の最新のコードです。

締め切りを数える部品が訪問者ごとに違って見える問題は、入力した瞬間に結果を返すコマンドパレットの検索UIでも同じ時刻基準の問題として現れます。

アクセシビリティ

装飾だけ止めて状態は残す

九つとも、zipを3jnnueudで開いて受け取るコードそのままの状態で、prefers-reduced-motion: reduceの環境では常時再生の装飾だけを止め、状態そのものは保ったまま表示します。カウントダウン(01)は秒カードの弾みを止めても数字の更新は続き、ソーシャルプルーフ(04)のカウントアップも情報なので変わらず動きます。

役割と状態属性

特典ハイライト(03)の各行はrole="button"とaria-pressedで、割引料金(05)のスイッチはrole="switch"とaria-checkedで状態を伝えます。締切バナー(06)の進捗バーはrole="progressbar"とaria-valuenow、プロモFAQ(07)とプロモフッター(09)はどちらもaria-expandedで開閉を知らせ、申込フォーム(08)の結果文はrole="status"(aria-live="polite")なので成功・失敗がすぐ読み上げられます。

isolation の使いどころ

申込フォーム(08)のグローリングに使ったisolation: isolateは、MDNのisolationプロパティ解説にある通り、overflowに触れずに新しい重なりコンテキストだけを作る手段です。ここで使った以外のランディングページ用セクションはテンプレートカテゴリーにまとめてあり、サイト運営者の情報はサイト紹介ページで確認できます。

FAQ

この lp テンプレート html のカウントダウンは本当に動いていますか?

実際に動いています。ページを開いてからの経過時間ではなく、Date.now()で計算した目標時刻までsetInterval(1000)で毎秒数え直して表示しています。targetの計算に使う日数・時間・分・秒を実際のキャンペーン締切に合わせて差し替えるだけでそのまま使えます。

React版はCSS版と動作が違いますか?

いいえ、動作は同じです。選んだパネル・スイッチのオン/オフ・開いたFAQ項目のような状態はuseStateで保持し、変数3つ(duration・easing・color)はpropsで受け取ってCSS変数(--duration・--easing・--color)へそのまま橋渡しします。ソーシャルプルーフ(04)のカウントアップもuseEffectの中で同じrequestAnimationFrameの処理をそのまま使います。

申込フォーム(08)は実際にメールを送信しますか?

いいえ、正規表現による形式チェックとSVGチェックの描画までが実際に動く範囲で、送信先のサーバーはつながっていません。submitイベントを検査するところまでがこのテンプレートの役目で、実際に使うときはその内側に本物のAPI呼び出しを足せば、検証・シェイク・チェック描画のロジックはそのまま再利用できます。

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

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