引用 デザイン CSS 9選 — コピペで決まる引用ブロック
引用 デザインとは、他人の一文を自分の文章の中にきれいに座らせるための引用ブロックのことです。このページにはそのブロックを9種、実際に動いた状態で並べてあり、グリッドの下のコードはそっくりコピーしてブログにコピペで決まる形にしてあります。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 横バーが伸びて開く引用
- 02 大きな引用符が跳び出す引用
- 03 本文の真ん中に降りてくるプルクオート
- 04 背景が左から右に満ちる引用ボックス
- 05 両端の引用符が内側へ滑り込む引用
- 06 話し手が一拍遅れて現れる出典行
- 07 インクの塊へ反転する引用
- 08 背後に大きな引用符が浮かぶ引用
- 09 ホバーで次の文に変わる引用
9つは難易度順ではなく、引用が文章の中で果たす仕事の順に並べてあります。読者が最初に出会う基本形の左バー(01)から始まり、飾りが文より先に出て(02)、文が本文を横切って(03)、箱が色で満ち(04)、両端から枠が締まり(05)、話し手が一拍遅れて現れ(06)、色がまるごと反転して重みが乗り(07)、背景が深くなった末に(08)、最後にだけ手に応えて動きます(09)。9種とも同じ色・間隔・イージングのトークンを分け合うので、1つの記事にいくつ混ぜてもトーンはずれません。
01横バーが伸びて開く引用
ブログ本文の基本の引用です。左の太いバーが scaleY で上から下へ満ちて場所を作ると、文が2行、出典が一拍ずつずれて落ち着きます。バーと文が同時に動く区間の累積変化面積は画面の 3.683% として測れました — 雑誌記事にそのまま使える無難な1枚です。
// バー — 上から下へ伸びる(scaleY)。カットではなくスケールなので面積がフレームごとに分かれて入る
.qb__bar {
border-radius: $r-pill;
background: $color;
}
.qb.is-demo .qb__bar {
transform-origin: top center;
animation-name: qbBar;
animation-duration: $duration; // 2s ループ
animation-timing-function: $easing;
animation-iteration-count: infinite;
}
@keyframes qbBar {
0% { transform: scaleY(0); }
26%, 80% { transform: scaleY(1); }
90%, 100% { transform: scaleY(0); }
}
// 文2行は同じ keyframes で百分率だけずらす — animation-delay 無しで一拍差が出る
@keyframes qbLine1 {
0%, 10% { opacity: 0; transform: translateY(8px); }
34%, 76% { opacity: 1; transform: translateY(0); }
80%, 100% { opacity: 0; }
}
02大きな引用符が跳び出す引用
ヘルプやガイドの冒頭に合う1枚です。文の左の大きな引用符が cubic-bezier(.2, 1.4, .4, 1) のカーブで一度跳ねて 1.14 倍まで行き過ぎてから着地し、本文2行が左から滑り込んできます(累積面積 3.828% ・強度 71.7)。跳ねるカーブは y が 1 を超える特殊なイージングなので、transform-origin: left bottom を併せて根元が動かないようにします。
// アイコンのポップ — 1を超えて戻ってくるカーブ(オーバーシュート)
.qm.is-demo .qm__mark {
transform-origin: left bottom;
animation-name: qmMark;
animation-duration: $duration;
animation-timing-function: $ease-pop; // cubic-bezier(.2, 1.4, .4, 1)
animation-iteration-count: infinite;
}
@keyframes qmMark {
0% { opacity: 0; transform: scale(.2); }
6% { opacity: 1; transform: scale(.92); }
22% { transform: scale(1.14); } // 着地前に一度行き過ぎる点
30%, 78% { opacity: 1; transform: scale(1); }
90%, 100% { opacity: 0; transform: scale(.9); }
}
// 本文のずれ入りは animation-delay — 無限ループでも位相はそのまま保たれる
.qm.is-demo .qm__l2 { animation-delay: 180ms; animation-fill-mode: backwards; }
03本文の真ん中に降りてくるプルクオート
雑誌が本文の間に差し込む大きな文です。前後の小さな地の文は steps(1, end) のカットで先に敷かれ、大きな文2行が animation-delay 60ms・160ms の差で降りてきます(累積面積 4.747% ・強度 119.7)。大きな文はどちらも opacity のランプを使わず translateY だけで動きます — その理由は落とし穴の節で測定値とともに書きます。
// 大きな文 — 最初から不透明のまま18px上から降りる(フェードの中間値がない)
.qp.is-demo .qp__a { animation-name: qpDropA; animation-delay: 60ms; }
.qp.is-demo .qp__b { animation-name: qpDropB; animation-delay: 160ms; }
.qp.is-demo .qp__a,
.qp.is-demo .qp__b {
animation-duration: $duration;
animation-timing-function: $easing;
animation-iteration-count: infinite;
animation-fill-mode: backwards; // 遅延の間も先頭フレームを敷く
}
@keyframes qpDropA {
0%, 2% { opacity: 1; transform: translateY(18px); }
16%, 80% { opacity: 1; transform: translateY(0); }
86%, 100% { opacity: 1; transform: translateY(18px); }
}
04背景が左から右に満ちる引用ボックス
ポイント・ヒントの箱に近い1枚です。::before で敷いた淡い背景が scaleX に transform-origin: left を組み、左端から右へ満ちていき、ラベルは充填が半ばのときにカットで、文は充填を追って滑りながら現れます(充填の面積が大きく、累積面積は 21.95% で9種中最大)。実用では箱に手を乗せるとラベルの色が transition-delay 80ms で一拍遅れて濃くなる仕草も付いています。
// 満ちる背景 — 内容より下の層(z-index)に敷き、左原点から scaleX で満たす
.qt::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background: $color; // ライラック
opacity: .3;
}
.qt.is-demo::before {
transform-origin: left center;
animation-name: qtFill;
animation-duration: $duration;
animation-timing-function: $ease-out;
animation-iteration-count: infinite;
}
@keyframes qtFill {
0% { transform: scaleX(0); }
18%, 74% { transform: scaleX(1); }
96%, 100% { transform: scaleX(0); } // 引きは入り(18%)より遅く — 退場フレームがポスターに勝たないように
}
05両端の引用符が内側へ滑り込む引用
インタビュー記事・調査レポート向けです。文の両端に ::before・::after で置いた大きなセリフの引用符が外から内へ滑り込み枠を締め、文は中央で一度浮かびます(累積面積 4.822%)。引用符はアイコンではなくタイポグラフィのグリフなので font-family: Georgia で書体を変えて装飾的な形を作り、文より上下に大きくはみ出させて置くのがこの1枚の核心です。
// 両端の引用符 — 絶対配置のグリフが内側へ締まる(不透明のまま、フェードなし)
.qs__body::before,
.qs__body::after {
position: absolute;
z-index: -1;
font-family: Georgia, "Times New Roman", serif;
font-size: 52px;
color: $subject-lilac;
opacity: .9;
}
.qs__body::before { content: "\201C"; top: -28px; left: -20px; }
.qs__body::after { content: "\201D"; bottom: -28px; right: -20px; }
@keyframes qsMarkBefore {
0% { opacity: .9; transform: translateX(-30px) scale(.7); }
28%, 68% { opacity: .9; transform: translateX(0) scale(1); }
92%, 100% { opacity: .9; transform: translateX(-30px) scale(.7); }
}
// 閉じ引用符(qsMarkAfter)は方向だけ +30px に反対 — 2つのグリフが同じフレームで動いて入りが最大変化になる
06話し手が一拍遅れて現れる出典行
編集インタビュー・顧客の言葉に使う1枚です。文が先に落ち着いた後、イニシャルの丸が scale で跳び出し、名前と肩書きが続いて浮かび、最後に下線が左から右へ scaleX で満ちます(累積面積 3.433% ・強度 111.5)。本文・丸・名前の登場が 6〜24% の区間で重なって複数が同じフレームで動き、下線だけが 28% まで待ってから続きます — なぜその配置が必要かは落とし穴の節の最初の話です。
// 出典行 — 丸(左)と名前・肩書き(右)を横に並べ、下端に下線を置く
.qc__src {
display: flex;
align-items: center;
gap: $sp-3;
padding-bottom: $sp-2;
position: relative;
}
.qc__src::after { // 下線 — 左原点から満ちる
content: "";
position: absolute;
left: 0; right: 0; bottom: 0;
height: 2px;
border-radius: $r-pill;
background: rgba(23, 20, 26, .8);
transform-origin: left center;
}
@keyframes qcDisc { // イニシャルの丸 — 文と重なって跳び出す(オーバーシュート)
0%, 6% { opacity: 0; transform: scale(.3); }
18% { opacity: 1; transform: scale(1.12); }
24%, 62% { opacity: 1; transform: scale(1); }
76%, 100% { opacity: 0; }
}
07インクの塊へ反転する引用
ポートフォリオの宣言・製品哲学のように、重みを乗せて言いたいときの1枚です。インク色のブロックが clip-path の inset で左から右へ拭われて現れ、その上の白い文が steps(1, end) のカットで一気に反転します — steps と clip-path を同じ keyframes に混ぜると補間が黙って死ぬので、ブロック(ワイプ)と文(カット)は2つのアニメーションに分けました。インク背景の上の白い文字のコントラストは 18.24:1 で、累積面積は 20.317% 、強度 203.3 は9種中最も強いです。
// インクのワイプと文のカットは分ける — steps と clip-path は一緒に補間されない
.qi.is-demo {
animation-name: qiWipe; // イージングのワイプ
animation-timing-function: $ease-out;
}
.qi.is-demo .qi__l1,
.qi.is-demo .qi__l2 {
animation-name: qiCut1;
animation-timing-function: steps(1, end); // 1フレームのカット
animation-iteration-count: infinite;
}
@keyframes qiWipe {
0% { clip-path: inset(0 100% 0 0 round 16px); }
16%, 74% { clip-path: inset(0 0 0 0 round 16px); }
92%, 100% { clip-path: inset(0 100% 0 0 round 16px); }
}
@keyframes qiCut1 {
0%, 17% { opacity: 0; }
21%, 70% { opacity: 1; }
74%, 100% { opacity: 0; }
}
08背後に大きな引用符が浮かぶ引用
エッセイの書き出し・書籍カバーの一文に合う1枚です。背後で 120px の大きな引用符がゆっくり浮かび上がり(ライラック 22%)、背景の模様になると、手前の文は z-index で一段上にくっきり残ります(累積面積 4.192%)。ウォーターマークがゆっくり育つ 46% の間に文を先にカットで登場させてフレームを重ねるのが設計です。
// 巨大引用符 — 文の一段下(z-index: 0)でゆっくり浮かぶ
.qw__mark {
position: absolute;
z-index: 0;
font-family: Georgia, "Times New Roman", serif;
font-size: 120px;
color: $subject-lilac;
opacity: .22;
}
// 文は steps カットで登場してから上へ — keyframes 内の timing-function で区間ごとに分ける
@keyframes qwBody {
0% { opacity: 0; transform: translateY(10px); animation-timing-function: steps(1, end); }
10% { opacity: 1; transform: translateY(10px); animation-timing-function: $easing; }
30%, 60% { opacity: 1; transform: translateY(0); }
74%, 100% { opacity: 0; }
}
09ホバーで次の文に変わる引用
ランディングヒーローの下で回る一文です。3つの引用を grid-area: 1 / 1 で同じマスに重ねて一度に1つだけ見せ、グリッドのプレビューでは steps(1, end) のカットが 33%・67% の地点で文を入れ替えます(累積面積 1.697% と最も静かですが、強度 96.7 で鮮やか)。カードにマウスを乗せると JS がグリッドのループを外して次の文へ進み、「次の引用」ボタンはキーボードの経路として残ります。
// 3つの文を同じマスに重ねる — 高さは最も長い文が決める
.qx__swap { display: grid; min-height: 48px; }
.qx__line {
grid-area: 1 / 1;
opacity: 0;
transition-property: opacity;
transition-duration: 140ms; // 実用の入れ替えは短いオパシティ遷移
}
.qx__line.is-on { opacity: 1; }
// プレビューのループはカットで入れ替え — 33% のちょうどで2つの文が同時に変わる(重なりも空白もない)
@keyframes qxLine1 {
0% { opacity: 1; }
33%, 100% { opacity: 0; }
}
@keyframes qxLine2 {
0% { opacity: 0; }
33% { opacity: 1; }
67%, 100% { opacity: 0; }
}
どこで壊れるか — 落とし穴
最初に壊れたのは、代表画像が空の舞台になることでした。06 の初版は4つの要素(本文・丸・名前・下線)を順に1つずつ立てていて、本文が1フレームのカットでまるごと消える瞬間の変化量が他の全部に勝ち、累積変化面積 0.1% の空の黄色い舞台がポスターに出てしまいました。直し方は2つ — 入りを 6〜24% の区間に重ねて複数が同じフレームで動くようにし、引きは要素ごとに別の区間で 12% 以上かけたフェードで流しました。測り直すと 06 は累積面積 3.433% ・動いたフレーム 18/23 で戻ってきました(24 枚を撮って前後を比べるので、比べる間は 23 個です)。
2番目はテキストフェードの半透明の幽霊でした。03 の大きな文を最初は opacity のランプで浮かせたところ、文字が半分透けたまま重なって読めるフレームがそのままポスターに選ばれました — ピクセル単位で遡るとそのフレームの混合率は 0.5 前後でした。文字が入る場所ではランプを全面的にやめました。03 は最初から不透明のまま translateY だけで降り、08 は steps(1, end) のカットで登場してから動き、地の文も全部カットです。直した後の 03 の文の混合率は 0.87 以上に上がりました。
3番目は 09 の二重進行でした。カードにマウスを乗せると次の文へ進むようにし、ボタンも同じ役割にしていたところ、マウスでボタンを押す瞬間に進入(1進む)とクリック(もう1進む)が連なって2文を飛ばしました。ボタンは event.detail が 0 のキーボード操作だけを受け取るように分けました — マウスの経路は hover が、キーボードの経路はボタンがそれぞれ1回ずつ担います。プレビューのカット位置を 33%・67% のちょうどに合わせたのも同じ系統です — 片方の文が消れてから次が現れると、1フレームの空白ができてちらつきます。
最後は舞台の色ではなく舞台の外でした。05 の大きな引用符は position: absolute で文の上下にはみ出して置かれており、回転・拡大で大きくなった境界ボックスは overflow: hidden があっても scrollHeight にそのまま数えます。オフセットを上下 28px 以内に抑えて、320px の電話画面(縦の余白 174px)でもはみ出しが 0 になるよう前もって計算してあります。9種のソースはこの記事が測った値のまま zip に入っており、開くパスワードは zqpvs5yk です。
アクセシビリティ
動きを減らしてほしいという設定(prefers-reduced-motion: reduce)では、9種とも同じ原則に従います — 自分で回る登場演出は止め、引用そのものは完成した状態で残します。静的な規則を先に書いてその上に .is-demo のループを重ねる構造なので、アニメーションを切っても 01 のバーは満ちたまま、04 の箱は色が付いたまま、08 のウォーターマークは浮かんだままです。reduce ブロックの宣言にすべて !important が付いているのは、プレビュー側がクラスを重ねて積んだ形なので特異度で負けないようにするためです。09 は最初の文を固定し、ボタンの経路はそのまま生きています。基準は MDN の prefers-reduced-motion に従いました。
文字のコントラストは実際のピクセル値から測りました。
| 場所 | 前・後の色 | 実測 | 基準 |
|---|---|---|---|
| 01 カードの文字 | インク / クリーム | 17.11:1 | AA・AAA 合格 |
| 02・06 舞台の文字 | インク / イエロー | 12.63:1 | AA・AAA 合格 |
| 03 大きな文(22px 800) | 白 / オレンジ | 3.32:1 | AA 大きい文字 合格 |
| 07 ブロックの文字 | 白 / インク | 18.24:1 | AA・AAA 合格 |
| 04・08 舞台の文字 | インク / 白 | 18.24:1 | AA・AAA 合格 |
| 03 地の文(11px) | クリーム 88% / オレンジ | 2.7:1 | 本文用ではない — 舞台の飾り |
03 の地の文はデモの舞台を飾る小さな文にすぎず、実際の記事には入れない値です。本物の部品は大きな文だけで、22px の太字なので AA の大きい文字の基準(3:1)を超えています。引用の意味は HTML の側に持たせます — <blockquote> で包み、09 のように変わる文には aria-live="polite" を、見えない重なりの文には aria-hidden を付けるのがこのページの原則です。引用要素の標準は MDN の blockquote にまとまっています。
文字を動かすもっと多くの方法は CSS テキストアニメーション 9種に、他人のレビューを並べるセクションは レビュー・ソーシャルプルーフ セクション 9種にそれぞれまとまっており、このサイトがデモをどう描いて測ってから記事を書くかは 紹介に書いてあります。
FAQ
引用符は CSS の ::before で入れてもいいですか?
いいです。05 のように content: "\201C" で入れると HTML がきれいなままですし、CSS が作った文字の扱いはスクリーンリーダーごとに違うので、純粋な飾りは本文の外に置く方が無難です。ただし引用符が意味に必要なら、HTML の中に直接入れる方が安全です。
プルクオートと blockquote は別のタグですか?
違います。HTML のタグは <blockquote> の1つで、プルクオートは使い方の名前です — 他人の文章を引用するのか(01・02・04)、自分の文章から一文を大きく抜いて強調するのか(03・07・08)の違いでしかありません。9種で変わるのは大きさと置き場所であって、選ぶ要素ではありません — ただし外側の殻まで同じではなく、09 は <blockquote> を使わず <figure> の中に <p> を3行並べ、03 は前後の文脈の文ごと <section> で包んでいます。
自分のブログの色に合わせるにはどこを直せばいいですか?
各 style.scss の一番上にある $duration・$easing・$color の3変数だけを変えます。$color がバー(01)・引用文(05)・充填背景(04)の色になり、React 版は同じ場所を props で受け取ります。舞台の色は触らなくて大丈夫です — 部品の色だけ入れ替わる構造です。