CSSのconic-gradientで直線的な模様を作る2022年12月19日 11時11分

この記事はCSS Advent Calendar 2022の19日目の分です。


CSSのconic-gradient(扇形グラデーション)関数を使うと、円グラフや集中線のような表現ができます。ここであえて扇形の中心角の部分に注目し、直線的な模様を作ってみることはできないでしょうか? いくつか試してみました。

例1. 二重のボーダー

「二重のボーダー」のデモ

「二重のボーダー」のCSSコード
p {
  --outer-border-width: 1em;
  --inner-border-width: 1em;
  --outer-border-top-left-color: #1bf;
  --inner-border-top-left-color: #35b;
  --inner-border-bottom-right-color: #dc6;
  --outer-border-bottom-right-color: #3a8;
  margin: 0;
  padding: 1em;
  border: calc(var(--outer-border-width) + var(--inner-border-width)) solid transparent;
  background:
    conic-gradient(from 180deg at var(--outer-border-width) var(--outer-border-width), var(--outer-border-top-left-color) 270deg, transparent 270deg) no-repeat border-box 0 0 / calc(100% - var(--outer-border-width)) 100%,
    conic-gradient(from 180deg at var(--inner-border-width) var(--inner-border-width), var(--inner-border-top-left-color) 270deg, transparent 270deg) no-repeat border-box var(--outer-border-width) var(--outer-border-width) / calc(100% - 2 * var(--outer-border-width) - var(--inner-border-width)) calc(100% - 2 * var(--outer-border-width)),
    conic-gradient(from 0deg at calc(100% - var(--inner-border-width)) calc(100% - var(--inner-border-width)), var(--inner-border-bottom-right-color) 270deg, transparent 270deg) no-repeat border-box calc(var(--outer-border-width) + var(--inner-border-width)) var(--outer-border-width) / calc(100% - 2 * var(--outer-border-width) - var(--inner-border-width)) calc(100% - 2 * var(--outer-border-width)),
    conic-gradient(from 0deg at calc(100% - var(--outer-border-width)) calc(100% - var(--outer-border-width)), var(--outer-border-bottom-right-color) 270deg, transparent 270deg) no-repeat border-box var(--outer-border-width) 0 / calc(100% - var(--outer-border-width)) 100%;
}

「二重のボーダー」の画像

例2. 矢印

「矢印」のデモ

「矢印」のCSSコード
p {
  --arrow-bg-color: #aaa;
  --arrow-color: #ff5;
  --arrow-angle: 120deg;
  --arrow-from-angle: calc(180deg + var(--arrow-angle) / 2);
  --arrow-outer-angle: calc(360deg - var(--arrow-angle));
  --arrow-margin-left: 1em;
  --arrow-width: 2em;
  --arrow-center: calc(var(--arrow-width) / 2);
  --arrow-offset: 1em;
  margin: 0;
  padding: 1em;
  border: solid transparent;
  border-width: 0 0 0 calc(var(--arrow-margin-left) + var(--arrow-width));
  border-radius: 0.5em;
  background: #ddd no-repeat border-box var(--arrow-margin-left) 0 / var(--arrow-width) 100%;
  background-image:
    conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(1 * var(--arrow-offset)), var(--arrow-bg-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
    conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(2 * var(--arrow-offset)), var(--arrow-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
    conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(3 * var(--arrow-offset)), var(--arrow-bg-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
    conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(4 * var(--arrow-offset)), var(--arrow-color) var(--arrow-outer-angle), var(--arrow-bg-color) var(--arrow-outer-angle));
}

「矢印」の画像

例3. 星形

「星形」のデモ

「星形」のCSSコード
p {
  --outer-block-angle: 6deg;
  --outer-inline-angle: 22deg;
  --cone-angle: calc(90deg - var(--outer-block-angle) - var(--outer-inline-angle));
  --cone-top-left-color: #bde;
  --cone-top-right-color: #cae;
  --cone-bottom-right-color: #ecc;
  --cone-bottom-left-color: #ffb;
  margin: 0;
  padding: 2em 3em;
  background:
    conic-gradient(from calc(90deg + var(--outer-block-angle)) at 0 0, var(--cone-top-left-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 0 0 / 50% 50%,
    conic-gradient(from calc(180deg + var(--outer-inline-angle)) at 100% 0, var(--cone-top-right-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 100% 0 / 50% 50%,
    conic-gradient(from calc(270deg + var(--outer-block-angle)) at 100% 100%, var(--cone-bottom-right-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 100% 100% / 50% 50%,
    conic-gradient(from var(--outer-inline-angle) at 0 100%, var(--cone-bottom-left-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 0 100% / 50% 50%;
}

「星形」の画像

例4. 吹き出し

「吹き出し」のデモ

「吹き出し」のCSSコード
p {
  --bg-color: #fed;
  --padding: 1em;
  --border-width: 0.5em;
  --border-color: #f93;
  --border-radius: 1em;
  --balloon-tail-angle: 60deg;
  --balloon-tail-from-angle: calc(90deg - var(--balloon-tail-angle) / 2);
  --balloon-tail-width: 2em;
  --balloon-tail-height: calc(1.15470054 * var(--balloon-tail-width)); /* 1.15470054 = 2 / sqrt(3) */
  --balloon-tail-bottom-offset: 1em;
  margin: 0 0 0 var(--balloon-tail-width);
  padding: var(--padding);
  border: var(--border-width) solid var(--border-color);
  border-radius: var(--border-radius);
  background: var(--bg-color);
}
p::after {
  content: '';
  position: absolute;
  display: block;
  width: calc(var(--balloon-tail-width) + var(--border-width));
  height: var(--balloon-tail-height);
  background:
    conic-gradient(from var(--balloon-tail-from-angle) at calc(2 * var(--border-width)) calc(var(--balloon-tail-height) / 2), var(--bg-color) var(--balloon-tail-angle), transparent var(--balloon-tail-angle)) no-repeat,
    conic-gradient(from var(--balloon-tail-from-angle) at 0 calc(var(--balloon-tail-height) / 2), var(--border-color) var(--balloon-tail-angle), transparent var(--balloon-tail-angle)) no-repeat;
  transform: translate(calc(0em - var(--padding) - var(--balloon-tail-width) - var(--border-width)), calc(var(--padding) + var(--border-width) - var(--border-radius) - var(--balloon-tail-height) - var(--balloon-tail-bottom-offset)));
}

「吹き出し」の画像

感想

やってはみたもののどうも微妙です。直線的な模様ならだいたいはlinear-gradient関数で表現できますし、要素を切り抜くのならclip-pathプロパティのほうが自由度が高いです。conic-gradient関数を使えば記述量を減らせるかというと、そういうこともそんなになく、やはり目的外の利用はあまりうまくいかないのかもしれません。

今回ひとつ得られた教訓は、背景画像を使って疑似的にボーダーを表現する場合、疑似的なボーダーと同じ大きさの透明なボーダー(border: <border-wdith> solid transparent)を指定したほうがよいということです。そうすることで、overflow: autoで内容がはみ出したときなどにスクロール領域が疑似的なボーダーにかからず、より自然に見えます。

CSSの絶対配置の要素の静的位置矩形2022年12月11日 12時52分

この記事はCSS Advent Calendar 2022の11日目の分です。


CSSでpositoin: absolute(絶対配置)の要素の位置を指定するときには、topleftrightbottomプロパティ(またはこれらを一括指定するinsetプロパティ)がよく使われます。もし絶対配置の要素にそれらのプロパティが指定されていなかったら、その要素の位置はどこになるでしょうか?

その場合、絶対配置の要素は原則として「その要素がpositoin: static(静的配置)だった場合の位置(静的位置矩形; static-position rectangle)」に置かれます。絶対配置の要素の親要素がインライン要素だった場合、絶対配置の要素自身がdisplay: inline(インライン要素)かdisplay: block(ブロック要素)かによって位置が異なってくることになります(インライン要素の子要素が絶対配置のときのデモ)。

この挙動をうまく利用すれば、絶対配置の要素の親要素にposition: relativeをつけて回らなくても、絶対配置の要素を期待する位置に置けることがあります。その場合、細かな位置の調整にinsetプロパティなどを使うことはできないので、transform: translate(...)marginプロパティを使うことになります。

フレックスアイテムが絶対配置のとき

「原則として」というからには例外もあります。絶対配置のフレックスアイテムでinsetプロパティなどが指定されていないものは、フレックスコンテナの位置に置かれます。

(「フレックスコンテナ」はdisplay: flexまたはdisplay: inline-flexが指定された要素、「フレックスアイテム」はフレックスコンテナの子要素です。)

グリッドアイテムが絶対配置のとき

グリッドアイテムが絶対配置のときはちょっと複雑です。グリッドコンテナが静的配置なら、絶対配置のグリッドアイテムでinsetプロパティなどが指定されていないものは、グリッドコンテナの位置に置かれます。

グリッドコンテナが静的配置でないなら、絶対配置のグリッドアイテムでinsetプロパティが指定されていないものは、gridプロパティなどで指定されたグリッド領域の位置に置かれます(グリッドアイテムが絶対配置のときのデモ)。

(「グリッドコンテナ」はdisplay: gridまたはdisplay: inline-gridが指定された要素、「グリッドアイテム」はグリッドコンテナの子要素です。)

とはいえ、フレックスボックスやグリッドと絶対配置を組み合わせるとCSSのコードがだいぶ複雑になるので、普段は避けたほうがよいと思います。

CSSでスクロールバーの有無によるがたつきをなくす2022年12月09日 09時42分

この記事はCSS Advent Calendar 2022の9日目の分です。


CSSのボックスモデルにおいては、ボーダーの内辺とパディングの外辺の間にスクロールバーが配置されます。最近はどのOSでもオーバーレイスクロールバー(スクロールバーが内容の前面に覆いかぶさるようなもの)が主流となり、スクロールバーが存在してもしなくても内容の幅が変わらないようになっています。一方、クラシックスクロールバー(スクロールバーが常に表示されるようなもの)が使われる環境では、overflow: autoな要素において内容がはみ出すときとはみ出さないときで内容の幅が変わってきます。

クラシックスクロールバーが使われる環境でも内容の幅を一定に保ちたいという場合は、scrollbar-gutterプロパティを使います。scrollbar-gutter: stableを指定すれば、スクロールバーが表示されないときでもスクロールバーと同じだけの領域が確保され、内容の幅はその分狭くなります。左右中央に配置したいのにスクロールバーの領域の分だけずれて困るというときは、scrollbar-gutter: stable both-edgesを指定することで、左右どちらにもスクロールバーと同じだけの領域が確保されます。

scrollbar-gutterプロパティの各値を指定した例

scrollbar-gutterプロパティを指定した効果の図

(上図はWindows 10 Firefox nightly 109.0a1での表示)

scrollbar-gutterプロパティはoverflow: hiddenな要素にも適用されますが、overflow: visibleな要素には適用されません。また、scrollbar-gutterプロパティの効果は、縦スクロールバー(縦書きなら横スクロールバー)にのみ影響します。

オーバーレイスクロールバーが使われる環境では、scrollbar-gutter: stableの効果はありません。

使用事例

使用事例としては、@about_hiroppyさんの紹介している「HTMLのdialog要素によるモーダルダイアログが開いているときに、背景の文書をスクロールさせない」というのが考えられます(scrollbar-gutterプロパティを指定した例指定しなかった例)。

html {
  scrollbar-gutter: stable;
}
html:has(dialog:modal) {
  overflow: hidden;
}

クラシックスクロールバーが使われる環境では、文書をスクロールさせないためにoverflow: hiddenを指定すると、スクロールバーが消えて文書の内容ががたついてしまいます。scrollbar-gutter: stableをあらかじめ指定しておけば、スクロールバーが消えてもその分の領域が確保され続けるので、文書の内容ががたつきません。

CSSでモーダルダイアログの背景をスクロールさせないようにできるかもしれない2022年12月01日 01時41分

この記事はCSS Advent Calendar 2022の1日目の分です。


HTMLのdialog要素を使うとモーダルダイアログを表現できます(使い方によってはモードレスダイアログも表現できます)。ただし、そのままだとモーダルダイアログを開いているときに、マウスホイールなどによってダイアログの背景(文書全体)までスクロールしてしまいます。

モーダルダイアログの背景をスクロールさせたくない場合、これを書いている現在のCSS仕様草案によれば、以下の記述で実現できるはずです(デモ)。

dialog {
  overscroll-behavior: contain;
}

しかしながら、この方法はChrome canary 110では期待通り動作しますがマウスホイールによるスクロールは防げますが、矢印キーやPageUp/PageDownキーによるスクロールは防げず、Firefox nightly 109では動作しません。

このあたりの事情はちょっと複雑で、

となっています。上述のCSSコードが将来的にも機能するかどうかは不透明です。

overflow: hiddenを使ってスクロールを防止する方法でも、:has疑似クラスと組み合わせることで、CSSのみでモーダルダイアログの背景のスクロールを防止できるそうです

HTMLのa要素にはhref属性を指定しなくてもよい2022年10月20日 23時30分

HTMLのa要素はハイパーリンクを表す要素であり、リンク先のURLをhref属性に指定します。しかし、a要素の役割はそれだけではありません。HTML標準によれば、a要素は「リンクとなりうる箇所のプレースホルダー」として使うこともできます。この場合はhref属性を指定しません。

リンクとなりうる箇所の例として、ナビゲーションやタブUI、パンくずリストなどでの「現在の項目」があります。

<nav>
  <ul>
    <li><a href="/">ホーム</a></li>
    <li><a>最新記事</a></li>
    <li><a href="/archives">アーカイブ</a></li>
    <li><a href="/settings">設定</a></li>
  </ul>
</nav>

ReactなどJSXでa要素を生成する場合、href属性を指定しないためにはhrefプロパティにundefinedを指定します。

import React from "react";

type Item = {
  label: string;
  url: string;
  isCurrent: boolean;
};

type Props = {
  items: readonly Item[];
};

const Navigation: React.FC<Props> = ({ items }) => (
  <nav>
    <ul>
      {items.map((item) => (
        <li>
          <a href={item.isCurrent ? undefined : item.url}>
            {item.label}
          </a>
        </li>
      ))}
    </ul>
  </nav>
);

リンクのプレースホルダーとしてのa要素は、うまく使えばテンプレートやCSSの記述を簡潔にできます。覚えておいて損はないでしょう。

なお、a要素にhref属性を指定しないと聞いてname属性を指定するのかと思った人もいるでしょうが、現在のHTML標準ではa要素のname属性は廃止済みであり指定すべきでないとされています。