横に水平線のあるテキスト

おもに見出しなどで見かける、テキストの左右 (またはそのどちらか) に水平線を配置したスタイル。これを CSS で実現する方法について考えてみました。

重ねて隠すパターン

まず最初は、1 本の水平線を幅いっぱいに配置した上で、テキストに重なる部分を隠す、という方法です。

<h2><span>Hello</span></h2>

マークアップはこのように二重の要素が必要です。外側の h2 要素の擬似要素で水平線を作り、内側の span 要素に背景色を指定してテキスト部分を隠します。

h2 {
    position: relative;
    text-align: center;
}

h2:before {
    border-top: 1px solid;
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
}

h2 span {
    background-color: white;
    display: inline-block;
    padding: 0 0.5em;
}

コードもさほど複雑ではなく、悪くないように思えます。しかし背景が単色である必要があり、画像の場合には使えません。また単色の場合でも、祖先要素と同じ色をその都度指定する必要があり、使いまわす上でやや難があります。もちろんマークアップを二重にしなければいけない点もマイナスです。

はみ出させて隠すパターン

次は、内側の要素の擬似要素で大きなサイズの水平線を作り、外側の要素ではみ出した部分を隠す方法。マークアップは重ねて隠すパターンと同じく二重になっている必要があります。

h2 {
    overflow: hidden;
    text-align: center;
}

h2 span {
    display: inline-block;
    padding: 0 0.5em;
    position: relative;
}

h2 span:before,
h2 span:after {
    border-top: 1px solid;
    content: "";
    position: absolute;
    top: 50%;
    width: 99em;
}

h2 span:before {
    right: 100%;
}

h2 span:after {
    left: 100%;
}

水平線は内側の span 要素の :before:after 擬似要素を左右に配置して表現します。これらに 99em など充分な幅を持たせ、はみ出した分は外側の h2 要素に指定した overflow: hidden により切り取ります。もちろん擬似要素の幅は 999% とか 9999px とかでもいいです。

重ねて隠すパターンと比べると、コードはやや複雑なものの、背景のスタイルを問わないという利点があります。しかし外側の要素に指定する overflow プロパティにはマージンの相殺やドロップシャドウの描画などで子要素への副作用があり、できれば利用を避けたいところです。また擬似要素の幅にいわゆるマジック・ナンバーというか、論理的ではない値を指定しなければいけない点に気持ち悪さもあります。二重のマークアップがいまいちなのは重ねて隠すパターンと同じ。

Flexbox パターン

最後は flexbox (flexible box) を利用したパターンです。

<h2>Hello</h2>

マークアップはこのとおり 1 つの要素でいいです。

h2 {
    display: flex;
    align-items: center;
    text-align: center; /* for no-flexbox browsers */
}

h2:before,
h2:after {
    border-top: 1px solid;
    content: "";
    display: inline; /* for IE */
    flex-grow: 1;
}

h2:before {
    margin-right: 0.5em;
}

h2:after {
    margin-left: 0.5em;
}

(CSS の flexbox 関連プロパティは、現時点では多くの環境でベンダー接頭辞が必要ですが、このコード例では記述を省略しています。ベンダー接頭辞を含むコードは デモ で確認してみてください)

まず display: flex によって h2 要素が「flex コンテナー」になり、その子要素が「flex アイテム」になります。水平線を表現するのは h2 要素の :before:after 擬似要素です。「flex コンテナーの直接の子要素であるテキストは匿名の flex アイテムに包まれる」という仕様なので、「左の水平線」「テキスト」「右の水平線」という 3 つの flex アイテムが並ぶことになります。

align-items は flex アイテムの並ぶ方向に対して垂直方向のアラインメントを指定するプロパティです。この値を center とすることでテキストと水平線が上下中央に配置されます。また flex-grow: 1 によって、左右の余ったスペースを埋めるかたちで水平線が広がります。なお、IE10 と 11 では擬似要素の display プロパティに inline block inline-block のいずれかを指定しないと表示されないという現象があり、あわせて指定しています。

この flexbox パターンは多くの点で前の 2 パターンより優れています。まず、祖先要素と同じスタイルを繰り返す必要もなく、子要素のスタイルに影響を与えることもない、ほかの要素から独立した可搬性の高いスタイルである点。そして、本来は必要のない部分を描画をした上で隠すといった遠回りなアプローチではなく、「テキストの左右にコンテンツ幅いっぱいの水平線がある」という本来のデザイン意図をそのまま論理的にコードで表現できている点。

余分なマークアップを必要としないところも大きな利点ですが、それはセマンティクス上の理由もさることながら、こういったスタイルの可搬性や論理的な表現のためにも重要だと思います。

flexbox の仕様は二転三転している上、ブラウザーの実装状況も複雑ですが、こういったほかへの影響の少ないちょっとした装飾から少しずつ採用を進めてみるのもいいんじゃないかと思います。