以下の内容はhttps://let.blog.jp/tag/CSSより取得しました。


Edge でスクロールバーの色が変わった
最近気づいたら変わってました
黒いです
以前は気にならなかったので たぶん変わったのは Edge 123 か 124 だと思います
Chrome は今のところ変わっていません

変わったのは トップレベル(ドキュメント全体)のスクロールバーの色です
自分で div に overflow: auto をつけてスクロールバーを出してもここは変わっていないようです

色はダークテーマかどうかを反映してそうです
ためしに html 要素のスタイルの color-schema を変更してみると light だと白く dark だと黒くなりました
OS 側でダークテーマを ON にしてるとそれを引き継いで黒くなるのでいつもと違って見えたということみたいです

ダークテーマを切替可能なページなら設定に応じて color-schema を切り替えてるので自然ですが ダークテーマに対応していないページだと color-schema は設定してないことも多く見た目はライトテーマなのにスクロールバーだけ OS の設定に合わせてダークテーマになっていて不自然です

後から出てきたブラウザ仕様に合わせてページを書き換えるのは嫌なところもありますが ダークテーマをサポートしないページでも

html {
color-schema: light;
}

だけはつけておいたほうがいいかもしれません
CSSStyleSheet から CSS の文字列を取り出したい
const css = new CSSStyleSheet()
css.replaceSync(text)

これで作った CSSStyleSheet オブジェクトから CSS のテキストを取り出したいです

目的は text の変数に入ってる CSS を有効な形に修正することです
HTML や CSS は構文がおかしくてもブラウザ側で不正なものは適当に修正してくれるのでその結果がほしいです

HTML なら

const div = document.createElement("div")
div.innerHTML = `<div><p>aa`
div.innerHTML
// '<div><p>aa</p></div>'

というふうにできるのですが CSS ってこういうのがないです
cssRules にルールが入ってますが 色々分かれてしまっています
いい感じに取り出すのは難しそうかなと思ってたのですが 単純に個々の cssRules の cssText を取得して結合すればいい感じになりました

「@」 を使ったものやネストなどに不安がありましたが MDN にあるサンプルコードを適当にコピペして試した感じではほぼ完全に復元できました
以下が確認済みです

@font-face
@keyframes
@media
@page
@container
@layer
@namespace
@counter-style
@supports
@property
@scope

@import は replace / replaceSync で使えないので確認してません
@charset は出力されなかったですがパース後には元の文字コードを保持する必要がないのでそういうものなのだと思います

もしかするとマイナーなところで出力できない部分があるかもしれないですが 基本的なものでは問題なく使えるレベルだと思います

const formatCSS = (text) => {
const css = new CSSStyleSheet()
css.replaceSync(text)
return [...css.cssRules].map(rule => rule.cssText).join("\n")
}

少し気になったところでは ネストするとインデントがおかしくなります

console.log(formatCSS(`
.foo {
.bar {
.baz {
}
}
}
`))
.foo {
.bar {
.baz { }
}
}

読むことを目的としてないので実害はないですが ネストの対応はこの辺まで完璧じゃないようですね

ちなみにこの方法 自分で作った CSSStyleSheet 以外の style タグや link タグでも使えました
link タグの場合は取得する CSS ファイルが同じオリジンである必要があります

const sheetToCSS = (sheet) => {
return [...sheet.cssRules].map(rule => rule.cssText).join("\n")
}

console.log(sheetToCSS(document.querySelector("style").sheet))
console.log(sheetToCSS(document.querySelector(`link[rel="stylesheet"]`).sheet))
CSS の適用範囲を制限したい
こんな HTML があって

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

.article の中にだけスタイルを当てたいです
ただしそのスタイルは自分で書くものではなくユーザー入力です
なのでセレクタで .article の中だけが対象になっている保証はないです

最近は CSS をネストできるので ユーザー CSS の構文が正しいことを確認したあとに

const style = `
.article {
${original_style}
}
`

みたいにすればいいかなと思ったりもしました
ですが

.article {
body:has(&) {
background: black;
}
}

とすれば body にスタイルを当てられます

ShadowDOM を使ったほうがいいのかなと思ったものの slot の中にはスタイルが当たりません

<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)

const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.innerHTML = `<slot/>`
</script>

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

slot を使わず ShadowDOM の中に .article の子要素を持ってきてしまえばスタイルは当たりますが あまり良いやり方に思えないんですよね

<script type="module">
const css = new CSSStyleSheet()
css.replaceSync(`
p { color: red; border: 1px solid pink; }
`)

const article = document.querySelector(".article")
const root = article.attachShadow({ mode: "open" })
root.adoptedStyleSheets = [css]
root.replaceChildren(...article.childNodes)
</script>

<h1>H1</h1>
<p>p</p>

<div class="article">
<h1>a.H1</h1>
<p>a.p</p>
</div>

(追記) (コメントありがとうございます)
@scope でもネストと同じで突破できそうと思ってましたが いろいろ試してみたところ大丈夫そうでした
& はスコープ開始のセレクタを書くのと同じらしいですが そもそもスコープ内しか対象にしない機能なので body などの外側の要素を選択しても効果がないです

あとは } が多くて @scope を抜けてしまうみたいのを許可しなければ大丈夫そうです
ネストのときは すぐにダメそうとわかって あまり考えてなかったですが CSS って HTML の innerHTML みたいな感じで CSSStyleSheet から完全な CSS 文字列を簡単に作れないんですよね
標準機能だけで構文エラーを修正したものを作れるといいのですけど
この辺はライブラリに任せたほうがいいかもしれません

続き → CSSStyleSheet から CSS の文字列を取り出したい
flexbox 内の SVG 画像のサイズ
普段あまり SVG 画像を使わないということもあってか flexbox と組み合わせたときに予想外な動きになって困りました

<!doctype html>

<style>
.row {
display: flex;
}
</style>

<div class="row">
<div><img src="00.svg"></div>
<div>00.svg</div>
</div>
<div class="row">
<div><img src="01.svg"></div>
<div>01.svg</div>
</div>

[00.svg]
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#ffc8aa" />
<circle cx="100" cy="100" r="80" fill="#b45829" />
<text x="100" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>

[01.svg]
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#ffc8aa" />
<circle cx="100" cy="100" r="80" fill="#b45829" />
<text x="100" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>

このページを表示すると 00.svg は期待通りに表示されますが 01.svg は表示されません
01.svg の img のサイズが 0x0 になって何も表示されないです

(確認用)

img に height: 100% を設定すると 高さを行の高さにでき それに合わせて幅も確保され表示されるようになりますが 親の div のサイズは幅が 0 なのは変わらず 文字と画像が重なります

画像は全部こうなのかと思いましたが PNG などではならなかったです
また SVG でも これが発生する SVG 画像と発生しない SVG 画像がありました
違いを探すと 00.svg と 01.svg のように viewBox か width+height かの違いでした
viewBox を使う場合は width と height は決まっていないので flexbox の中で自動で計算される場合は最小の 0x0 になるようです
width と height があればそれが画像サイズとなるのでちゃんと表示されます

SVG 画像側に width と height がないなら img タグの属性側に viewBox に合わせた width と height をつければ解決できます
ただしその場合 CSS で max-width を指定しても属性で指定しただけの height が確保されてしまうような違いが出ます
可能なら SVG ファイルの方に width と height を追加したほうが良さそうです
word-break と overflow-wrap
テキストの折り返しをしたいときの CSS についてです

まずは普通にこんな HTML を書きます

<!DOCTYPE html>
<style>
div {
width: 160px;
background: powderblue;
font-family: monospace;
}
</style>
<div>
aaaaa bbbbb ccccc ddddd eeeee fffff
</div>

幅が 160px しかないので 入り切らない部分はスペースの位置で改行されてこうなります

aaaaa bbbbb ccccc
ddddd eeeee fffff

スペースがない場合は

<div>
aaaaabbbbbcccccdddddeeeeefffff
</div>

折り返しされず 親要素のエリアをはみ出してすべてが 1 行で表示されます

aaaaabbbbbcccccdddddeeeeefffff

これの対処のために使われるものの一つが word-break です

word-break: break-all;

これをつけると結果はこうなります

aaaaabbbbbcccccdddddee
eeefffff

入り切らなくなるところで折り返ししてくれます
しかし これを使うと最初のようなスペースがある場合に問題が起きます
スペースで折り返されず ギリギリまで詰め込んで無理やり改行することになります

aaaaa bbbbb ccccc dddd
d eeeee fffff

コンソールみたいなところだとこれでいいのですが 通常の文章ではあまり見た目が良くないです
これを期待どおりにするのが overflow-wrap です

overflow-wrap: break-word;

これを設定すると 基本は通常通りスペースで折り返してくれて スペースがないような場合は入り切らなくなるところで折り返ししてくれます

aaaaa bbbbb ccccc
ddddd eeeee fffff
aaaaabbbbbcccccdddddee
eeefffff

なので基本は overflow-wrap を使うで良いと思います

また 特殊なケースで 記号のみの場合は word-break で折り返しされないというのがあります

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

みたいなものは word-break だと折り返されずはみ出します
overflow-wrap だと折り返しができます

記号と言っても折り返されるものとそうでないものがあります

確認用ページ
https://nexpr.gitlab.io/public-pages/word-break-chars/example.html

クエリパラメータで追加の文字を指定できるようにしてます
?chars=abc のように書くと a と b と c が追加されます
全体を display:none にしておくとページ遷移のときにちらつきが減る
最近は CSR のものばかりで最初から HTML があるものはあまり作らないので気にしてなかったのですが 久々にそういうページを作ったら module のロード中に初期化前の状態が見えていまいちなものになりました
CSS のロードも ESM でやると適用されるのが後からになりますからね
また CSS を同期的にしても JavaScript で初期状態のクラスを設定する場合 module が全部ロードされてから処理が行われるので それまでに違う状態の画面が表示されてしまいます
ほぼ一瞬ですが チラつきになりますし あまり気持ちのいいものではないです

一瞬関係ない画面が映るよりは display: none をつけて何も表示されないほうがマシかなと思って display: none にしてみると 思ってた以上に良い動作になりました
一瞬の真っ白な画面が挟まりません
前のページの画面が維持されて display: none が消えて表示される状態になって始めて画面が更新されました

ただ 500ms くらいが境目のようで 600ms とか 1s ほどの遅延になると一瞬真っ白な画面が挟まります
それ未満の 300ms などだと 真っ白な画面は発生しません
display: none を外してみると 一瞬関係ない画面は出るので ブラウザがいい感じにしてくれてるようです

注意点として body の中身が全く表示されない状態の必要があります
メインのコンテンツの div を display: none にしてもその外に表示される要素があったら それだけが表示されます
body ごと display: none にしておくのがいいかもですね

試せるページ↓

遅延300ms+display:noneあり
遅延300ms+display:noneなし
遅延800ms+display:noneあり
遅延800ms+display:noneなし

下の方にあるリンクでメインとサブのページを交互に移動できます
URL の delay と hide で遅延させる時間や display: none の有無を変更できます
詳細はページ内のクエリパラメータの説明参照です
CSS の zoom プロパティは存続するみたい
以前なくなりそうと書いたけど結局どうなったんだろうと調べてみました

https://groups.google.com/a/chromium.org/g/blink-dev/c/V7q43bgutbo/m/-7jneTl8CQAJ

の最後のコメントを見る感じ CSSWG で正式仕様にするよう決まったみたいで 削除はされないそうです
リンク先にすごく長い議論の本文がありますが 長すぎてそこまで読んでないです

標準の仕様化するなら今の一貫性のない挙動じゃなくて もっと見直してほしいと思いますが 標準化する理由が互換性の問題なので今の動作に合わせた形にしかならない気がします
また歴史的経緯で変な仕様が増えるのか
Grid でヘッダーとフッターとサイドバーのレイアウトを作るとき
3 x 2 のグリッドにしてこんな感じにしてる例をよく見ます

1 2
3 4
5 6

Header: 1, 2
Main: 3
Sidebar: 4
Footer: 5, 6

こうするとわざわざ ヘッダーとフッターは横 2 マス分って設定しないといけないです
サイドバーとメインコンテンツの区切り位置をヘッダーやフッターにも反映したいならともかく それらと関係ないなら一つのグリッドにまとめなくてもいいと思います
単純に縦に 3 分割して その中の 2 のところを横に 2 分割するで十分です
HTML 構造的にヘッダーもメインもサイドバーもフラットになって欲しいならありかもですが こういう場合は構造的にボディ部分は一つにまとまってその中で分割してほしいです
Grid ってフラットになる分 画面とコードの位置関係の対応が HTML だけだと分かりづらくなります
無理にフラットにしないほうがいいと思います
Chrome 120 で CSS のネスト時にセレクタをタグ名から始められるようになる
https://chromestatus.com/feature/5070369895743488

h1 {
a {
color: dodgerblue;
text-decoration: none;
}
}

これができるようになります

これまでは 内側のセレクタは記号から始める必要がありました
ID やクラスなどのセレクタは記号から始まるので問題ないですが タグ名を書くときは簡単に書けませんでした

上記の CSS だと a のセレクタは無効です
:is() を使ったり & を使う必要があります

h1 {
:is(a) {
color: dodgerblue;
text-decoration: none;
}
}
/* or */
h1 {
& a {
color: dodgerblue;
text-decoration: none;
}
}

事前に CSS を変換するツールだと 基本的にこの一手間が不要だったので少し不便です
事前に変換するなら速度がそこまで重要視されないので 先の方まで読んでしまって セレクタなのかプロパティ名なのか判断できます
しかし ブラウザだと先まで読むことを基本許可しないので タグ名から始められないのは仕方ないものでした
この辺は実装されるまで仕様を決める段階でも色々議論されてたようです

なので無理なものと思っていたのですが Chrome 120 からはそれが緩和されてタグ名から始められるようになったようです
パフォーマンス面ではやはり劣るみたいですが できる限り高速になるようしているそうです
基本は declaration (color: red; みたいの) としてパースし始めて プロパティでないと判断でき次第 rule (h1 {} みたいの) として再パースするようです
詳細な説明は仕様の Implementation note のところに書かれてました
https://drafts.csswg.org/css-syntax/#consume-block-contents

ShadowDOM を使ってるとスコープが小さくなるので タグ名だけのセレクタを使うことが多くなります
毎回 :is() を書くのに疲れてきていたので これはとても嬉しいですね
Lit で CSS-in-JS したい
Lit の 3.0 が正式リリースされたので久々に Lit を使っています
React で CSS-in-JS ライブラリを使っていると Lit でスタイルを適用したいときに少し不便です
一応 styleMap という directive があるのですが 単純に style 属性を設定するものです
最近ブラウザで使えるようになったネストするセレクタは書けません

やりたいものはこういうのです

customElements.define("foo-bar", class extends LitElement {
render() {
return html`
<div
class=${style`
border: 2px solid #aaa;
padding: 12px;
:is(input) {
padding: 4px;
}
`}
>
<label>name</label>
<input>
</div>
`
}
})

div の class のところでスタイルを書いて 自動生成されるクラス名が class に渡されて設定されるというものです
だいたい Emotion です
ただ そこまで複雑なことはしなくてもいいです
ネストはブラウザ側の機能で実現できるので 単純にクラス名を作って設定するだけでいいです
それなら自分で簡単にできるかなと試してました

LitElement のクラスでは styles という静的プロパティを用意しておくことで 自動でそのスタイルをコンポーネントに適用してくれる機能があります
しかし こういう動的に更新するものには使えないです
なので自分で adoptedStyleSheets を追加します
コンストラクタのタイミングでは ShadowRoot を作ってくれないので createRenderRoot をオーバーライドして ここで追加します
あとは style 関数が呼び出されたタイミングで中身を更新します

customElements.define("foo-bar", class extends LitElement {
constructor() {
super()
this._cssss = new CSSStyleSheet()
}
createRenderRoot() {
const root = super.createRenderRoot()
root.adoptedStyleSheets.push(this._cssss)
return root
}
style(template, ...values) {
const class_name = "c"
this._cssss.replaceSync(`.${class_name} {${String.raw(template, ...values)}}`)
return class_name
}
render() {
return html`
<div
class=${this.style`
border: 2px solid #aaa;
padding: 12px;
:is(input) {
padding: 4px;
}
`}
>
<label>name</label>
<input>
</div>
`
}
})

これで動くようになりました
ただこれだと最後に呼び出された style 関数の分しかスタイルを保持していません
replaceSync の代わりに insertRules にすれば追加できますが 増えていく一方です

同じ場所の更新ならルールを更新できるといいのですけど
テンプレートリテラルの機能を使ってみようかとも思いました
しかし あくまでソースコード上の同じ場所ということしかわかりません
CSSStyleSheet が CustomElement ごとなので 別インスタンスと混ざることはないですが map で繰り返すようなケースは対応できません

Lit の directive が使えるかと思いましたが directive は lit-html 側の機能なので LitElement と統合されていないです
CustomElement の参照は得られず 対応する Part の更新だけに焦点が当てられています
また Part が消えた場合の処理を記述する方法も用意されていません

いい方法がみつからないので とりあえず全部更新する方法にしました
update をオーバーライドすれば render の前後に処理を入れられるので 更新時に使ったルールだけを残し 他は削除します
スタイルの更新のための処理が増えてきたので 間にクラスを設けることにしました

class StylitElement extends LitElement {
constructor() {
super()
this._cssss = new CSSStyleSheet()
this._style_num = 0
this._style_used_rules = new Set()
}
createRenderRoot() {
const root = super.createRenderRoot()
root.adoptedStyleSheets.push(this._cssss)
return root
}
style(template, ...values) {
const class_name = "c" + (this._style_num++).toString(36)
const index = this._cssss.insertRule(`.${class_name} {${String.raw(template, ...values)}}`)
this._style_used_rules.add(this._cssss.cssRules.item(index))
return class_name
}
update() {
this._style_num = 0
this._style_used_rules.clear()
super.update()
const rules = [...this._cssss.cssRules]
for (const rule of rules.toReversed()) {
if (!this._style_used_rules.has(rule)) {
const index = rules.indexOf(rule)
this._cssss.deleteRule(index)
}
}
}
}

このクラスを使います
複数の style を使って map の中でも使うようにしています
また再レンダリングを兼ねて input の入力を全体で同期するようにしました

customElements.define("foo-bar", class extends StylitElement {
static properties = {
name: {},
}
constructor() {
super()
this.name = ""
}
render() {
return html`
<div class=${this.style`display: flex; flex-flow: column; gap: 10px;`}>
<div
class=${this.style`
border: 2px solid #aaa;
padding: 12px;
:is(input) {
padding: 4px;
}
`}
>
<label>name</label>
<input .value=${this.name} @input=${event => this.name = event.target.value}>
</div>
<div
class=${this.style`
border: 2px solid #f8d;
padding: 20px;
:is(input) {
padding: 4px;
}
`}
>
<label>name</label>
<input .value=${this.name} @input=${event => this.name = event.target.value}>
</div>
${["#f00", "#0f0", "#00f"].map(color => {
return html`
<div
class=${this.style`
border: 2px solid ${color};
padding: 12px;
:is(input) {
padding: 4px;
}
`}
>
<label>name</label>
<input .value=${this.name} @input=${event => this.name = event.target.value}>
</div>
`
})}
</div>
`
}
})

とりあえず動くようになりましたが render の呼び出しのたびに style で作るルールは全部新規に作り 古いのは消すという処理です
あんまりパフォーマンス的に優れてそうにないです
でもこれ以上がんばるならもう Emotion 使ったほうがいいような気もしてきます
ただ Emotion にしても同じルールが存在するかを確認して差分更新をするのでそこまで改善されるのかは疑問です

簡単にできそうで効率良くなるかもってところだと やっぱりルールの更新方法でしょうか
差分更新はがんばらないにしても もう少しなんとかできるかもと思います
CSSStyleSheet の削除って index 指定でしか消せず ひとつひとつ index を取得してから消すのって効率悪そうです
ルールの全置き換えなら 最初から新規の CSSStyleSheet を作って それと置き換えてしまう方がよいかもしれないです
それか 配列でルールの文字列を保持して最後に replaceSync を使うかでしょうか
display: contents でラップするときの問題
display に contents を指定すると DOM のツリーとしては存在するのに存在しないように扱わせることができます
React の Fragment に近いイメージです
要素をまとめるための要素が存在するのに 無いものとして扱わせたいときに使います

例えば

<style>
.row {
display: flex;
}
</style>

<div class="row">
<div>A</div>
<div class="wrap">
<div>B</div>
<div>C</div>
</div>
<div>D</div>
</div>

div.row は display: flex なので横並びになりますが 2 つめの子要素は普通の div なのでここは縦並びになります

ABD
C

B と C を囲む div.wrap を無いものにしたいときは これに display: contents をつけます
これで

ABCD

という並びにできます
flex だと div.wrap も flex にすればいいのであまり必要なかもですが grid だともっと役立ちます

そんな display: contents ですが 完全に無いものとしては扱えず不便なときもあります
例えば 子要素にスタイルを当てるとき

<style>
.container > * {
margin-top: 10px;
}
.contents {
display: contents;
}
</style>

<div class="container">
<div>A</div>
<div class="contents">
<div>B</div>
<div>C</div>
</div>
<div>D</div>
</div>

こういうケースでは div.contents にマージンは効かず この div を透過して B と C にマージンが設定されることもありません
A と D にだけマージンが設定されます

React などをつかっていると div.container の子要素はコンポーネントで生成していて イベントをまとめて受け取るためなどで display: contents の要素でトップレベルをラップしてるとかもありえます
そうなるとこういう子要素に共通でスタイルを当てたいというときにうまく動かないケースが出てくるのですよね

CSS セレクタの > や + などでも display: contents は透過してくれるといいのですけど
ドット絵を拡大表示するときは image-rendering: pixelated
IE 時代と違って 画像の拡大縮小が起きても自動できれいに表示してくれるので ブラウザ上で画像を拡大縮小表示するときのアルゴリズムを気にすることは特になかったです
ですがドット絵を拡大するときはなめらかにされると微妙なことになるので そのままドット感を残して拡大してほしいです
IE 時代は CSS に独自プロパティがありましたが Chrome でもあるのかなと調べると image-rendering を使えば良いみたいです

元のサイズ

dots

拡大サイズ(通常)

dots-large

拡大サイズ(pixelated)

dots-large
hr で水平線が表示されない
hr タグを書いているのに表示されていないところがありました
他のところは表示されています
devtools の Elements タブで見ても hr タグには特別なスタイルは当たっていないようです

原因は親要素が display: flex かつ縦並びになっていたことでした

<div style="
display: flex;
flex-direction: column;
">
<p>a</p>
<hr>
<p>b</p>
</div>

こういう HTML で再現できます

hr に width: 100% を指定することで hr を表示できます

ですが align-items のデフォルトは stretch なので width: 100% を指定しなくても横幅いっぱいになるはずです
実際の値を見てみても stretch になっていました
なぜ width 指定なしでうまく表示されないのでしょうか

もう少しあれこれ見てみると原因はユーザーエージェントのデフォルトスタイルシートでした
Chrome 系ブラウザでは hr のマージン関係のデフォルトがこうなっています

margin-block-start: 0.5em;
margin-block-end: 0.5em;
margin-inline-start: auto;
margin-inline-end: auto;

margin-inline が左右なのでこれが auto ということは中身のサイズに合わせて左右に自動でマージンが付きます
hr は中身が無いので幅が 0 になって表示されてないということでした

デフォルトは auto じゃなくて 0 でいいと思うのですけど
CSS の position:sticky で固定されたときだけスタイルを当てれるようになる
Chrome の現バージョン 116 で DevTrial です
https://chromestatus.com/feature/5072263730167808

詳しい説明
https://lilles.github.io/explainers/state_container_queries.html

position: sticky でスクロールしたときに要素を簡単に固定できるようになりましたが 固定されてるときだけスタイルを変えたいということはよくありました
それができるようになります

使い方はコンテナクエリを使って

@container state(stuck: top) {
#sticky-child { font-size: 75% }
}

のようになるそうです

HTML の例だと

<style>
#sticky {
container-name: my-menu;
container-type: sticky;
position: sticky;
top: 0px;
height: 100px;
}

#sticky-child {
background-color: orange;
color: white;
height: 100%;
}

@container my-menu state(stuck: top) {
#sticky-child { width: 50%; }
}
</style>
<div id="sticky">
<div id="sticky-child">
Sticky
</div>
</div>

できるようになるのは嬉しいのですが 少し手間なのが難点ですね
コンテナクエリが必須ですし contaner-type の指定も必要です

お手軽に擬似クラスで :stuck でいいと思うのですが パフォーマンスの懸念など色々あってコンテナクエリになったようです
CSS に相対カラーの構文が追加されるみたい
CSS Relative Color Syntax (RCS) というのが Chrome 118 で DevTrial のようです
https://chromestatus.com/feature/5205844613922816
https://www.w3.org/TR/css-color-5/#relative-colors

こういう感じです

div {
background: rgb(from var(--bg-color) r g b / 80%);
}

rgb や hsl などの色空間の関数に from で元になる色を指定してから 次に各値を指定します
構文的には

rgb(from 元の色 R G B / 透過率)

rgb 関数なので R と G と B を指定します
透過率は省略可です

R と G と B には これまでの rgb 関数のように 0~255 の数値や%で指定できます
相対と言いつつもこの%は 100% が 255 に相当するもので 元の色に対する割合ではありません

rgb(from red 100% 100% 100%)

と書けば rgb(255, 255, 255) = #ffffff と同じです
red は意味のないものになります

from の色を使うには 数値と%以外の特殊なもので r と g と b を使います
これを使うと元の色の R と G と B の値になります

rgb(from red r g b)

と書くと元の色そのままです

rgb(from red r r r)

と書くと G と B も R の値 (255) になるので rgb(255, 255, 255) です
元の色を使って計算するときは calc を使います

rgb(from rgb(100,100,100) calc(r * 1.1) calc(g * 1.5) calc(b * 2))

これの結果は rgb(110, 150, 200) になります
足し算で直接数値を足したい場合は小数値で扱うことになります
1 が 255 に相当します

rgb(from rgb(100,100,100) calc(r + 0.5) calc(g + 0.1) calc(b + 1))

これの結果は rgb(228, 126, 255) になります
0.5 は 128 なので 100 + 128 = 228 です
1 を足すと 255 を超えてしまうので 255 になります

rgb で使うよりも hsl で hue だけを変えたいみたいなケースのほうが使うかもですね
Chrome 118 で @scope 機能が追加される予定
https://chromestatus.com/feature/5100672734199808
https://drafts.csswg.org/css-cascade-6/#scope-atrule

CSS に @scope という機能が増えるようです

@scope (.foo) to (.bar) {
a { color: red; }
}

と書けば DOM のツリー上で .foo の内側で .bar の外側を対象に a タグの色を変えるということになります
ShadowDOM みたいなスタイルのスコープ機能が追加されるということですね
WebComponents を使うなら ShadowDOM でいいですが React 等のフレームワークの場合はコンポーネントごとに ShadowDOM を使わないのでスコープを制御し辛いです
なのでこういうのがあると便利になりそうです

これに合わせて Scope Proximity という新しい概念が追加されてスタイルの優先度にも変更があるようです
詳細度が同じ場合 セレクタの対象要素とスコープのルート要素間の距離が近いほど優先度が高くなります
スコープが指定されないこれまでのスタイル定義だと距離が無限(優先度最小)として扱われるようです

例えばタブがあって その中にフォームがあって タブとフォームにスコープが設定されていて フォームパーツは両方のスコープに入る場合 まずフォームスコープのスタイルが優先されて次にタブスコープのスタイルで 最後にページ全体のということのようです
優先度的には良さそうです
ただ詳細度の方が優先度が上なので やっぱり詳細度には悩まされそうです

構文的には

@scope [(<scope-start>)]? [to (<scope-end>)]? {
<rule-list>
}

なので scope-start や scope-end は省略できるみたいです
scope-end を省略すると

@scope (.dark-scheme) {
a { color: plum; }
}

CSS のネスト機能が使えるようになる前なら ネスト機能としても使えるので需要が高かったのかもしれません
でも今では先にネスト機能が使えるようになっているので あまりこのケースは使わなそうです

ネスト機能と同じようなことができますが 全く同じではなく少しだけ違いがあります
@scope に指定するセレクタ (上の場合の .dark-scheme) の部分は詳細度に含まれません
ネストの場合に詳細度に影響しないよう :where() を使うような挙動です
その場合でもスコープによる優先度があるので :where() を使うより @scope の方が優先度が高くなります

scope-start を省略する場合は その style タグの親要素がスコープのルートになります

<div>
<style>
@scope {
p { color: red; }
}
</style>
<p>ここは赤色</p>
</div>
<p>ここは赤色じゃない</p>

昔 style タグに scoped 属性がありましたが それに近いことができますね
display: none をアニメーションできるようになるらしい
https://chromestatus.com/feature/5154958272364544

これまでだと display: none があるとアニメーションできず すぐ消えてしまっていました
それが Chrome 115 でアニメーションできるようになるみたいです
徐々に薄くなったり小さくなったりするわけではなく 完全に none になるまでの間は表示されてる扱いで その他のアニメーションが有効になるというものです
薄くしたり 小さくしたり は自分で別の CSS プロパティをアニメーションさせて実装する必要があります

これまでだと そういうアニメーションを実行させてから 終わったのを JavaScript で検知して display: none にする必要がありました
今回の変更で アニメーション後に自動で display: none になるので少し楽になるという感じです

個人的には display: none ではなくて remove してしまいたいことのほうが多いので意外と使い所がなかったです
画面端に出てくる通知みたいなものだと 任意個数になるので事前に要素を作っておかずに その場で要素を作って使い終わると削除しますからね
ずっと実体が残ったままだとポップアップやダイアログでしょうか


少し使った感じだと

@keyframes hide {
0% { height: 100px; display: block }
100% { height: 0; display: none }
}

div {
height: 100px;
background: red;
}

.hidden {
animation: hide 2s linear 0s;
display: none;
height: 0;
}

のように .hidden に非表示状態のスタイルも書いておくとアニメーションしてくれませんでした
非表示にするときに hidden クラスをつけます

他のスタイルだと アニメーション中はアニメーションのスタイルが優先されて アニメーションが終わると直接指定されたスタイルに戻るというものです

@keyframes color {
0% { color: red; }
100% { color: pink; }
}

.color {
animation: color 2s linear 0s;
color: blue;
}

だと color クラスをつけると 赤からピンクに文字色が変わり アニメーションが終わると青色になります
そう考えると .hidden に display: none があっても問題ないように思うのですが .hidden の中に display: none があるとアニメーションされないです

display: none をキープするには アニメーション後にスタイルを保持する設定が必要です

.hidden {
animation: hide 2s linear 0s forwards;
}

のように forwards を指定するとアニメーションの最後のフレームのスタイルのままになります
でも直接書かれたスタイルじゃないので分かりづらく好きじゃない方法です
100% と同じ内容をスタイルに直接指定して動くようなって欲しいですね
CSS の zoom プロパティがなくなるらしい
https://chromestatus.com/feature/6535859207143424

非標準で Firefox だとサポートされてないものみたいです
event.path のときと同じ感じみたいですね

実際ズームしたいときは transform の scale 関数や scale プロパティで指定していたので zoom は特に使った覚えもないです

.zoom {
transform: scale(1.2);
/* or */
scale: 1.2;
}

代替があるなら無くても良さそうと思いましたが 全く同じ挙動ではないみたいで zoom を正式な機能にしたいという要望もあるようでした
違いは拡大のタイミングで レイアウトの前にズームするか後にズームするかが異なってるようです

scale だとレイアウト計算後の拡大・縮小なので 倍率が変わっても実体の大きさは変わらないとして扱われて 周りのレイアウトを壊しません
なので アニメーションに向いています
ですが レイアウトが決まった後に拡大するので 画面幅 100% いっぱいのページに対して全体を拡大するとはみ出ます
スクロールバーが出てきます
そういうことがあるので レイアウトを計算する段階で拡大を有効にしたい場合には使えません

これに対して zoom だと拡大・縮小されたものとしてレイアウトが計算されるのでスクロールバーは出ません
固定サイズのものを折り返しなしで並べるなど スタイルによっては出るケースもありますが scale と違って確実に出るわけではないです
Ctrl-+ などブラウザの機能としてのズームと同じ扱いです

こういう違いがあると zoom がなくなると困る人もいそうです
Safari (Webkit) もこの機能を実装していて Chrome に合わせて削除するかの議論がこの issue です
https://github.com/WebKit/standards-positions/issues/170

壊れるページが出てくると言ってますし 本当に削除できるのか疑問もあります
Chrome も壊れるサイトが多いとやっぱりやめるとかあるのかもしれません
現状の予定では Chrome 114 (次のバージョン) で Developer trial として削除されて 117 で正式に削除の予定のようです

また zoom には現在の倍率を知るという使い方もあったようで これができなくなるそうです

getComputedStyle(document.documentElement).zoom
// '1'

のように html 要素の計算済みスタイルの zoom を参照します
Ctrl-+ などブラウザの機能でズーム倍率を変更して 150% にすると '1.5' になります

Windows の機能で拡大縮小した場合も反映されます
150% 設定なら '1.5' になります
Windows の設定で 150% で さらにブラウザで 150% にズームすると掛け算されて '2.25' になります

倍率がわかって便利です

しかし html 要素に zoom スタイルがあたってると この機能がなくなります
スタイルとして指定された値が固定で返ってきます

html { zoom: 2 }

というスタイルがあると Windows の設定やブラウザのズーム倍率は無視され ずっと '2' になります
また Windows の設定で 150% なら デフォルトが '1.5' のはずなのに

html { zoom: 1.5 }

というスタイルを適用すると そのままの倍率ではなく さらに 150% の拡大が行われます
非標準というだけあってわかりづらく一貫性のない挙動です

設定せず参照のみであれば使えそうな気はしますが 現在は window.devicePixelRatio で同じ値が取れるのでこれはなくてもよさそうです



その後
sticky は背景グラデーションで綺麗に表示できない
見出し (sticky)

本文
本文
本文

見出し (sticky)

本文
本文
本文

の繰り返しがあるとき コンテナ要素の背景がグラデーションになってると 綺麗な表示にできない
sticky で上に張り付いているとき 非 sticky 要素は sticky 要素と重なってスクロールしていく
背景が透明だと重なって見えてしまうので そう見えないように通常は sticky 要素に背景色を指定する

sticky 要素が背景のグラデーションを無視した固定色なら単純に background-color 指定するだけ
でも背景のグラデーションを維持したい場合はスクロール位置に応じて背景色を後ろに合わせたグラデーションに揃えることになって現実的じゃない
透明にするのじゃなくて 裏側の色をコピーしてくる機能がほしい
複数の sticky 要素は重なってる
見出し (sticky)

本文
本文
本文

見出し (sticky)

本文
本文
本文

の繰り返しがあるとき 下にスクロールして 3 つ目の見出しがついてきてる状態では 3 つ目だけじゃなくて 1 つ目と 2 つ目もついてきてる
それらが重なっていて 一番上に 3 つ目があるから 3 つ目だけがついてきてるように見えるだけ

これは同じ高さならいいけど 2 つ目の見出しが長くて折り返しが入って高さが大きくなると 3 つ目がついてきてるときに 2 つ目のはみ出た分が見えてしまう

<style>
.container {
overflow: auto;
width: 200px;
height: 200px;
}
.head {
position: sticky;
top: 0;
color: white;
}
.body {
height: 300px;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
</style>

<div class="container">
<div class="head red">1</div>
<div class="body">body</div>
<div class="head blue">2<br/>2-2</div>
<div class="body">body</div>
<div class="head green">3</div>
<div class="body">body</div>
</div>

固定中の要素の前の固定中の要素は非表示にできるといいけど 現状の CSS ではそういうことはできないので 高さを揃えるか JavaScript でがんばるしかなさそう



[追記]

フラットにせず sticky にしたいものを区切りにして div で囲めれるなら囲むだけでそれだけで対処できた

<style>
.container {
overflow: auto;
width: 200px;
height: 200px;
}
.head {
position: sticky;
top: 0;
color: white;
}
.body {
height: 300px;
}
.red {
background: red;
}
.blue {
background: blue;
}
.green {
background: green;
}
</style>

<div class="container">
<div>
<div class="head red">1</div>
<div class="body">body</div>
</div>
<div>
<div class="head blue">2<br/>2-2</div>
<div class="body">body</div>
</div>
<div>
<div class="head green">3</div>
<div class="body">body</div>
</div>
</div>

sticky でついてくる状態は内側 div の範囲になる
直後の 「body」 が表示されてる間はついてくるけど内側 div がスクロールされ画面外にいくと ついてこなくなる
sticky と次の sticky が重なるとき 上に乗るのではなく 上にスクロールされていくので自然な動きになる
Parcel2 まだ問題多めな気がする
以前 Parcel1 を使ってたものを Parcel2 に移行しました
大きな問題はなさそうで 高速になって良くなったと思っていたのですが 使っているとまだ問題がありました

◯ ファイル名のハッシュ値が安定しない

ビルドするとファイル名にハッシュ値が付きますが 同じソースから複数回ビルドするとハッシュ値が変わることがあります
リリースノートによれば 2.7 で修正されたらしいのですが最新の 2.8.2 でも発生しています

複数の .js ファイルからインポートされる .js が .css をインポートして その .css の中で url(); で画像をロードしています
このときの画像ファイルで発生しています
ただ この CSS ファイルがロードする画像ファイルは 10 を超えるのですが 1 つでだけ発生しています

高速化を重視しているので並列化した結果どっちが先に処理されるか次第という感じなのかもしれません

ビルド結果をバージョン管理等に含める場合 これらも差分として扱われるので不便です

◯ CSS の順番が import 順にならない

CSS Modules を使っているのですが 出力される CSS の順番が期待どおりになりません

import foo from "./foo.css"
import bar from "./bar.css"

と書いた場合は foo が先で bar が後であることを期待します
しかし bar の方が先にくるので優先順位が想定どおりにならず表示が崩れました
これは 2.8 にアップデートしてから発生しています
依存関係やバンドル関係のアルゴリズムを変更したらしいので多分これが原因でしょうね

CSS をモジュール化しても複数のモジュールのクラスを 1 つの要素につけることがあるので順番は考慮してほしいものです
汎用的な共通スタイル部分を先にインポートしてそのモジュール固有のスタイルを後にロードしても共通スタイルが優先されたりします

これの対処のためにモジュール固有スタイルはすべてに不要なセレクタをつけて詳細度を上げるなんてことをしないといけなくなりました
flexbox を折り返したときに両端に配置する
(a) や (b) が何らかの要素で↓のように両端に配置したいです
「|」←が画面端です

|(a)(b)         (c)(d)|
| |

画面幅が小さくてはみ出る場合は折り返しをしたいです
ただし

|(a)(b)               |
|(c)(d) |
| |

のようにならず複数行でも両端にあってほしいです

|(a)(b)               |
| (c)(d)|
| |

単純に折り返すだけだとこういうのです

<style>
.container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.item {
width: 200px;
border: 1px solid #0003;
background: #ddf;
flex: none;
}
.margin {
margin: auto;
}
</style>

<div class="container">
<div class="item">a</div>
<div class="item">b</div>
<div class="margin"></div>
<div class="item">c</div>
<div class="item">d</div>
</div>

c と d の間で折り返されたくないので div にまとめます
また .margin 要素が b の隣で幅いっぱいになるだけで c が右揃えにならないので c の margin-left が auto になるようにします

<style>
.container {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.subcontainer {
display: flex;
gap: 10px;
}
.item {
width: 200px;
border: 1px solid #0003;
background: #ddf;
flex: none;
}
.margin {
margin: auto;
}
.margin + * {
margin-left: auto;
}
</style>

<div class="container">
<div class="item">a</div>
<div class="item">b</div>
<div class="margin"></div>
<div class="subcontainer">
<div class="item">c</div>
<div class="item">d</div>
</div>
</div>

space-between を使って両端揃えすると折り返しても 末尾が右揃えになってうまく行ったりしないかなと思ったのですがダメでした

<style>
.container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
}
.subcontainer {
display: flex;
gap: 10px;
}
.item {
width: 200px;
border: 1px solid #0003;
background: #ddf;
flex: none;
}
</style>

<div class="container">
<div class="left subcontainer">
<div class="item">a</div>
<div class="item">b</div>
</div>
<div class="right subcontainer">
<div class="item">c</div>
<div class="item">d</div>
</div>
</div>

この場合でも .right に margin-left: auto を付ける必要があります
CSS の :empty と :blank の意味が変わってた
以前は :empty も :blank も中身が空の要素を対象にするセレクタで 違いはこうだったはずです

:empty → 完全に空で空白文字すら含まない
:blank → 空白文字は含んでもいい

ブラウザが実装しているのは :empty のみで :blank は実装されていないので空白文字列があるだけで対象外になってしまう扱いづらいものでした
それが

:empty → 空白文字は無視して空の要素
:blank → input や textarea などのユーザー入力が空

になっていました

変更履歴を見ると 2018 年なので結構前に変わっていたようです
https://w3c.github.io/csswg-drafts/selectors/#changes-2018-02

しかしブラウザの実装はというと 以前から変わらず :empty は空白文字が入ると対象外ですし :blank は未対応です

ただ既存ページへの影響を考えると 本当にこの通り実装されるのか疑問もあります
古い :empty の仕様に従って対象から外すためにあえてスペースを入れてるようなことをしてるページもありそうですし(経験あり)
まぁ現状の使い勝手の悪い :empty を使ってるページ自体がほとんど無い気もしますけど……

:has だって jQuery が壊れるとか言って () の中に不正セレクタが入るとエラーになるようにするという仕様から外れるような修正があったくらいです
実装されてもなにかありそうな気はしますね
letter-spacing で最後にも余白できるのは仕様じゃなかったんだ
letter-spacing で文字間にスペースを入れると最後にも入ってしまいます
なので文字間ではなく文字の後にスペースが入るものと考えていました

ですが仕様では文字間に入れるのが正しくて 最後のスペースは正しくなかったようです
https://qiita.com/kazhashimoto/items/658205ad27f07235d370

でも現状のブラウザがどれもこうなってるらしいので 変えると今の挙動に合わせて工夫してるページの表示が崩れます
こういう状態だと修正されずこのままになりそうな気がします
関連の issue では新しくキーワードの追加などでこの挙動を改善する提案もありましたが 結構前のものでした
今のところそういうのが入りそうでもないのでずっとこのままかもしれませんね

似たもので flex に 1 を指定したときの挙動があります
仕様では 1 1 0 とみなされるはずですが ブラウザは 1 1 0% と解釈します
基本は同じですが % だと親の width または height が決まっていない場合に 0 と異なる結果になります
Parcel の lightningcss ではこれに関連して問題が出ていました
CSS の仕様に従って省略した結果 ブラウザがそれに従ってなかったのでバンドル後に見た目が違っていました
https://github.com/parcel-bundler/lightningcss/issues/283

互換性を優先すると こういうところで仕様との不一致がそのままになるのが不便なところですね
CSS で infinity が使えた
CSS で絶対表示されないような位置に移動するためにすごく大きな数値を設定するなど とても大きな値を設定したい ということが稀にあります

そういうときに適当に大きな値を指定していたのですが infinity が使えました
infinity だけだと単位を書けないので calc と合わせて使います

.foo {
width: calc(infinity * 1px);
}
.bar {
position: absolute;
top: calc(infinity * -1px);
}
.baz {
margin-left: calc(infinity * 1px);
}
.qux {
z-index: calc(infinity * 1);
}
.quux {
animation: anime1 calc(infinity * 1s) linear 0s;
}

px 以外に 時間等にも使えます
% や em や vw などもありますが 長さ系は infinity にするなら px と同じです
z-index は単位なしの数値なのでそのまま書ける気もしましたが calc が必要でした
仕様的には calc 用の定数みたいですね
-infinity, e, pi, nan などもあります
ページ全体をズームしたい
ということがあったものの現状 JavaScript からユーザーが操作できるブラウザのページ表示倍率は触れないはず
○%以上のズームは許可しませんとかすると使う側からして不便になるし わからないでもない

transform でいいかと思ったけど これはレイアウト計算後にズームするから思い通りにならない
だけど 全体をズームするなら問題ない気がする と思って試してみたら思いの外問題なく動いた

ページサイズを 2 倍にする

#root {
width: 50vw;
height: 50vh;
overflow: auto;
transform: scale(2);
transform-origin: 0 0;
}

流石にここまで大きいのはじゃまなので 125%

#root {
width: 80vw;
height: 80vh;
overflow: auto;
transform: scale(1.25);
transform-origin: 0 0;
}

body に設定すると overflow が効かないので #root みたいな要素を作ってそこに設定
すべての要素は #root の中に配置する
Portal 的な機能で ダイアログ等を body 直下に置くとズーム対象外になるので注意

自作ページならどうにかなるけど ネット上にあるページを拡張機能とかで制御するのには向いてなさそう
拡張機能にするなら直接倍率制御できるかも?
vanilla-extract はブラウザ上で使うものじゃないみたい
Emotion に近いライブラリを探していて 人気の CSS 系ライブラリを見ていたらみつかったのが
vanilla-extract

簡単に使い方を見た限りでは結構似てそう
https://vanilla-extract.style/documentation/styling-api/

バンドルはしたくないので skypack で試そうとすると

<script type="module">
import { style } from "https://cdn.skypack.dev/@vanilla-extract/css"
console.log(style({ color: "red" }))
</script>

「outdent__default.default is not a function」というエラー
ググってもそれっぽいのは出て来ない
skypack に対応してない?

StackBlitz を使ってみても別のエラー
この辺で動かないって中でどんな特殊なことしてるの?

そう思ってちゃんとドキュメントを見ると Emotion は目的が異なるものだったみたい
ランタイムで処理しなくていいように 静的な CSS ファイルをビルド時に生成するツールらしい
プロジェクトの説明やドキュメントの最初に Zero-runtime って書いてたけど zero なんとかって言うと 設定なしや依存関係なしをよく見るからそのへんだと思ってスルーしてた
最初に skypack 以外の CDN 使おうとして依存関係の解決で ESModules だとロードできなかったときに zero dependencies じゃなかったの?って少し疑問に思ったけどそういうことだったのか

CSS 生成用ツールだけあって ドキュメントには Setup のセクションがあって webpack や esbuild などビルドツールごとの設定方法が書かれてた
Chrome 104 で CSS の transform プロパティを個別に指定できるようになる
https://chromestatus.com/feature/5705698193178624

CSS の transform は translate や rotate などが混ざっていて少し特殊です

div {
transform: translate(200px, -100px) rotate(90deg) scale(1.2);
}

一つのプロパティが長くなって書きづらいですし JavaScript から動的に変更したいときや animation の設定時にとても面倒です
CSS のプロパティとして個別に設定したいと思ってましたがそれができるようになるようです

div {
translate: 200px -100px;
rotate: 90deg;
scale: 120%;
}

とても助かりますね
ちなみに Safari では結構前からサポートされてるようです
https://webkit.org/blog/11420/css-individual-transform-properties/
CSS 系に関しては Safari の方が Chrome 系よりも進んでますね
CSS/JavaScript の互換性を捨てたものでないのかな
別記事で CSS の詳細度が消えて欲しいって愚痴を書いていて そういえば JavaScript も互換性を理由にイマイチな仕様が色々残ってるなと思いました
ウェブ関係は ES2015 から JavaScript が大幅に進化して その他ブラウザの WEB API も進化してきました
ですが 古いページを見れるようにという理由で互換性を重視するので イマイチな仕様が残ったりです
廃止とされた機能もありますが 昔からある機能だと削除されることは全然なくて形だけのあまり意味がないものです
新規作成時に使わないほうがいいというだけで 消されることがないのはほぼ確定だし 新しいのを覚えるのも面倒だからと使い続ける人もいます
単純に使わなければ自分には関係ないというタイプの機能ならまだいいですが CSS の詳細度みたいに絶対関わってきて回避のために面倒を強いられるものもあります
typeof で null が "object" になるのも 見方を変えればおかしくはないのでしょうが 使うときには不便でしか無いです

直接 CSS や JavaScript を書かずに他言語で書いて CSS や JavaScript を生成するのも一つの方法ですが 手間が増えてますし ブラウザで直接動くわけじゃなく間に余計なものを挟むことになって気が進みません
型が欲しいから TypeScript を使うとか 関数型言語が好きだから Elm を使うとか そういうポジティブな理由でならまだいいですが JavaScript でいいけど 互換性のために変な仕様があるのが嫌だからというネガティブな理由では alt 系を使うのは気が進みません
特にその理由が昔のページを表示するために互換性を残すというのも不満に感じるところです

その他言語だと古いバージョン使えばいいという理由で 互換性を捨てられます
変化が大きすぎると Python みたいに移行が進まないとかはあるかもですが その他言語でもメジャーアップデートで徐々に古い機能が使えなくなってるのはよく見かけます
古いページを表示するためと言って互換性を残しているといつまで経っても古い機能を引きずることになって 他言語に比べると発展が妨げられます
最近ではウェブ以外でも Electron だったり WebView だったりでローカルアプリなどでも使われてますし いつまでも過去の負債を残してないで綺麗にして欲しいところです

そもそも更新もされない過去のサイトなんて どれだけの人が見てるんでしょうか
Google 検索でも古いページはめったに出てこないです
互換性を切ったとしても古いサイトを見る時は IE の互換モードでも使えば良いです
IE の完全終了をウェブの一区切りとして廃止機能や一貫性の無い部分を消して新しいものにしていってくれるといいのですけどね
CSS Layer より詳細度をなくして欲しい
CSS Layer が使えるようになって 普段の Chrome の更新のときよりも新機能が話題になってます
Layer でライブラリを下にして自分のコードを上にすれば 「詳細度が足りないから詳細度を上げるためになくてもいいセレクタを追加をする」 ということは必要なくなります
Layer もいいですが それよりも詳細度なんて余計なものをなくしてくれた方が良いです

詳細度があると結局自分のコード中で詳細度のせいでムダなセレクタ追加は起こりえます
そのたびに Layer を作って優先度をあげることはできますが それは本来の Layer としての使い方は違って後々困りそうです
:root:root みたいなのをつけるのが Layer に変わっただけみたいなものです
詳細度を消して 後にあるものが優先されるという単純なルールにしてれば余計な苦労は一気に無くなるのですけど
順番だけで決まれば Layer でライブラリを下にして自分のを上にするというのもロード順を変えるだけです
Layer はあってもいいですが 詳細度なんて厄介なだけな機能はなくしてほしいです

新し目な機能は使用率が少ないからと廃止されたりするのに 古い機能は互換性優先で消されないのでどうせ詳細度は消えはしないだろうとは思ってました
それでも新機能な Layer ブロックの中なら詳細度を無視するようにしても互換性は保たれるので Layer の中に配置すれば無視されるみたいのを期待してたのですが そんなことはなく Layer 内では詳細度によって優先度が決まります

無理やり詳細度をなくそうとすると 全部のブロックの詳細度を同じになるよう CSS を書き換えることになります
それをやろうとすると最大詳細度を計算してそれぞれのブロックのセレクタを見て何を足せばいいのかを調べてと複雑でした

.foo .bar {
color: red;
}
.baz {
color: green;
}

だと .baz にクラスが一つ足りないので html タグに付けたクラス名を追加して .html .baz {} みたいに書き換えます
それが Layer を使えば

@layer {
.foo .bar {
color: red;
}
}
@layer{
.baz {
color: green;
}
}

のように全部を単純に @layer {} で囲むだけで済むので楽にはなります
ただそれぞれのブロックを Layer とするのはどうかと思うので実用はしたくないですけど

そんな感じなので最近は直接 CSS を書かずにライブラリの CSS-in-JS 機能で要素に直接指定する方向にしてます
要素単位なのでセレクタの概念はなくて プログラムでの埋め込み順になるので詳細度に縛られることはなくなります

それにしてもなんでこんな百害あって一理なしみたいな詳細度なんてシステムを導入してまったんでしょうか
全体はタグで指定して あちこちで指定するのはクラスで 特定の 1 箇所だけなら id だから id>class>tag で優先されるようにすると順番どう書いてもうまく動いて楽だから とかでしょうか
CSS が作られた頃はページも単純だったので それで困らなかったのかもしれませんが 複雑になってくると足を引っ張るだけになるとわかりそうなものですけど
text-indent がないのに先頭行だけ開始位置がずれてる
inline-margin-ex01

上の画像のように先頭行だけ開始位置がずれてる場所がありました
外部 CSS も読み込んでるのでそこで余計な text-indent が設定されてるのかなと思い devtools で探してみたのですが みつかりませんでした
それ以外に先頭行だけずれる原因に心当たりがなくて devtools で適当にスタイルを外したり付けたりしていたら原因がわかりました
inline 要素に margin がついてるせいでした
そんな変な挙動だったっけ……

とりあえず inline で margin 付いてる要素を inline-block にして対処

上の画像を再現するコード

<!DOCTYPE html>

<style>
.parent {
width: 200px;
background: #eee;
display: flex;
}
.child {
flex: 1 1 0;
}
.child+.child {
border-left: 1px solid #aaa;
}
a {
margin: 0 10px;
}
</style>

<div class="parent">
<div class="child">
<a>ああああああああああああああああ</a>
</div>
<div class="child">
<a>アアアアアア</a>
</div>
</div>
折りたたみ Surface 用に JavaScript と CSS が増えるの?
https://forest.watch.impress.co.jp/docs/news/1276996.html

Windows とかでマルチディスプレイにまたがって表示してるのと同じ扱いでいいじゃん……
余計なもの増やさないでよーー

というか 別画面なら別のウィンドウでいいと思うのに
同じページとして連携したいなら別タブ間の postMessage 通信で済むし
今でもマルチディスプレイでそれぞれに最大化したブラウザのウィンドウ表示して メインディスプレイ側でテキスト打つとサブディスプレイ側でプレビューとかやってる
npm のバージョンリストに日付を表示する
npm の Version History のところは相対日付しか表示されていなくて 2 years ago とかが並んでてもあまり嬉しくないです
具体的にいつ頃なのかわからないですし このバージョンとこのバージョンの間はどれくらい空いてたのかを知りたい時とか不便すぎです

一応マウスを乗せるとツールチップで表示されますが ひとつひとつ乗せないとダメなのは面倒です
同時に見れないですし

そこで title 属性(ツールチップのテキスト)を表示するような CSS を用意しました

time:before {
content: attr(title);
color: #d0841e;
font-size: 11px;
margin-right: 20px;
}

このスタイルを npm のページで表示するようにすれば相対日付の左側に完全な日付が表示されます
≡メニュー
≡メニューをどう実装するかというのを見かけたので
その時の気分でよく変わるけど最近はこういう感じ

<div class="hover-target">
<div class="face">≡</div>
<ul class="menus">
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ul>
</div>

<style>
.hover-target {
display: inline-block;
}
.hover-target:not(:hover) .menus {
display: none;
}
.face {
display: flex;
width: 1.5em;
height: 1.5em;
justify-content: center;
align-items: center;
cursor: default;
box-shadow: 0 0 2px 1px #0003;
}
.menus {
border: 1px solid #bbb;
margin: 5px 0;
padding: 0;
list-style: none;
}
.menus li {
border-bottom: 1px solid #ddd;
padding: 5px 15px;
margin: 0;
cursor: pointer;
}
</style>
Parcel で CSS Modules だけ設定が違う理由
深く考えてなかったけど Parcel で CSS Modules だけプラグイン設定方法が他と違ってる

{
"modules": true,
"plugins": {
"autoprefixer": {
"grid": true
}
}
}

https://parceljs.org/css.html

ドキュメントだとオブジェクトをエクスポートするので特別な対応が必要と書いてる

モジュールを有効にして以下のコードをバンドルする

.foo{}
.bar{}
import styles from "./test.css"
console.log(styles)
parcel build --no-minify index.js
結果は
/*略*/
})({"qP9o":[function(require,module,exports) {
module.exports = {
"foo": "_foo_05711",
"bar": "_bar_05711"
};
},{}],"Focm":[function(require,module,exports) {
"use strict";

var _test = _interopRequireDefault(require("./test.css"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

console.log(_test.default);
},{"./test.css":"qP9o"}]},{},["Focm"], null)
//# sourceMappingURL=/index.js.map

クラス名のオブジェクトがエクスポートされてる

モジュールが無効だとこう
/* 略 */
})({"qP9o":[function(require,module,exports) {

},{}],"Focm":[function(require,module,exports) {
/* 略 */

処理は何もなし
エクスポート内容が無いときも空オブジェクトで常にエクスポートがあるなら共通でも良さそうだけど モジュールがあるときだけ追加の処理でエクスポートしてそう
Parcel 内部でその判断するためにフラグ形式でモジュールを使うかどうか判断必要みたい

エクスポートの必要性
モジュール使わないときは出力される CSS はそのままのクラス名
JavaScript 側での処理はいらずに HTML や JavaScript では CSS ファイルに書いたままのクラス名を書くだけ
モジュール使うときは一意な名前に変換されてる
その変換後の名前を知るために JavaScript では CSS をインポートして元のクラス名から変換後のクラス名を取得して DOM に設定する
未使用 CSS を削除するツール
たまにネットで見かけますが 個人的にはあまり信用してません
必要なところまで消されそうな気がして仕方ないです

CSS はクラスや属性名で指定できますが それらは静的なものとは限らないです
JavaScript から作られるものも多くあるわけですが 文字列結合など色々処理した結果できあがる文字列の場合もあります
不要部分を削除してしまうわけですから 多分使われてなさそう 程度で消すと動かなくなって重大な問題です

静的な解析だと限界がありますし 実際に動かして使われたかを判別する機能は Chrome の devtools についてます
ただそれも 実際にそのスタイルが使用される状態になる必要があります
一通りの操作はしてみても 特別な状態の組み合わせでしか起きないスタイルの指定があるかもしれません
すべての状態の組み合わせで試すなんて規模が大きいと無理がありますし CSS や HTML の書き方や JavaScript でクラスの指定など何らかのルールに従って書いてる前提でもないと確実にないって言い切るのはかなり難しいと思います

ただ 実際に使う操作を試して使っていないスタイルなら 特別な組み合わせでのみ使用されるケースがあってもとりあえず削除してもいいのかもしれません

作った側は使うと確実にバグが発生するのを認識していて 修正依頼が来たら修正する準備はできているのに 誰も使わない機能なせいで数年前からその状態のままという話を聞いたことがあります
業務で使うようなシステムになるとユーザ数も限られて割とあるのだとか
それに似た話で テストをするのが作った側ではなく 使う側がやることで 機能上できはするものの実際には使われないような使い方はバグがあっても気づかれず放置で 逆によく使う使い方は丁寧にチェックされるというやり方をしてるところもあるようです
誰も使わない機能や使い方でのバグを直しても時間の無駄ですし 使う側としては使う機能だけちゃんと動いてくれればいいですからね
バグが絶対許されない環境でもないならそれくらいのゆるさのほうが効率も良くていいのかもしれません
tailwindcss 便利かも
https://tailwindcss.com/

ちょっとした align とか margin/padding とか flex の方向とか
lit-html や hyperhtml で HTML 中に styleMap とか使って直接書こうかなって思うくらいなのも多いし入れとくと便利なのかも
ただこういうのって事前に大量のスタイル定義されるから重そうなのが気になるところ

ちゃんと見てないけど JavaScript でビルドとかあるし 使ってないのは含めないみたいな機能あるのかな
あればすごく良さそうなんだけど
でも JavaScript で文字列結合などからも追加できるから使ってるかを静的に判断するのってかなり難しそうだし無いかな
DOM の表示がおかしいと思ったら
要素の位置がおかしくて CSS 的にありえないような場所にいる
リロードしても変わらなくて 別のページで同じ構造でシンプルにしたらなぜか再現しない
さらにスタイルをを devtools の Element タブで書き換えてもなにも変化しない

あれこれ試した結果 devtools の Animation タブが原因だった
知らないうちに Pause all を押してて全アニメーションが停止状態になってた
ページ開いたタイミングではアニメーションの初期状態になってて Element タブのスタイルではそれが見えないので CSS が正しく効いてないように見えてた
Animation タブ 気をつけよう
デフォルトスタイル
h1 とか table とかに class 指定しなくてもデフォルトでスタイル設定したい
だけど h1 自体に設定してしまうと例外的にここだけ別のにしたいってときに面倒です
デフォルトスタイルで変更した部分を全部上書きしないといけないですし

かと言って h1.default にスタイルを設定して 全部の h1 に class="default" をつけるのも面倒です
JavaScript でロード時や DOM 更新時に class 属性がないなら "default" を自動設定も可能ですが あまりそういうことはしたくないです

いい方法ないか考えたところ

(1) デフォルトを使わない場合に "no-default" クラスをつけるようにする

スタイルでは :not で "no-default" があればデフォルトスタイル当たらないようにする

h1:not(.no-default) {}

デフォルト使わないだけでわざわざクラス必要なのってどうなの?

(2) クラスがないときにデフォルトスタイルをあてる

なにか class をつけるとデフォルトスタイルが当たらないようにする
デフォルトスタイルを使いたいけど class も必要なときのために "default" クラスをつけてもデフォルトスタイルをあてるようにする

h1:not([class]), h1.default {}

class 以外の属性を条件にスタイル当てるから class に設定いらないけどデフォルト解除したいときは class="" が使える
けど class で空文字か属性なしかでスタイル変わるのは分かりづらい感もある
class 属性あればいいので (1) みたいに "no-default" って書いてもいい
外部 stylesheet のロードも style タグにしたい
JavaScript は script タグでインラインもできて src タグで外部ファイルのロードもできる
なのに style はインラインで書けるだけで外部ファイルのロードはできない
link タグとかぱっと分かりづらいし href 以外に rel="stylesheet" って属性も必要

style に揃えたいと思って考えみたら import するだけでいけた

<!doctype html>

<style>@import url(style.css)</style>

<h1>a</h1>

Document の adoptedStyleSheets は全体に反映して欲しい
ShadowRoot のならその ShadowDOM の中で良いけど Document に設定したならすべての ShadowRoot にも反映して欲しい
ドキュメントなんだからコンポーネントも含めてグローバルでいいと思う
WebComponents 使ってると body 直下にルートコンポーネントを置いて ShadowRoot 外の要素なんてないことが普通
それだと Document に対する adoptedStyleSheets なんていらないし

一応こういう風に document.adoptedStyleSheets を継承させるベースクラスを作っておくことはできるけど毎回は面倒

class CustomHTMLElement extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" }).adoptedStyleSheets = [
...document.adoptedStyleSheets,
this.cssstylesheet,
]
}

static css = ``

get cssstylesheet() {
if (!this.constructor.cssstylesheet) {
const cssss = new CSSStyleSheet()
cssss.replaceSync(this.constructor.css)
this.constructor.cssstylesheet = cssss
}
return this.constructor.cssstylesheet
}
}

使用例⇩

const cssss = new CSSStyleSheet()
cssss.replaceSync(`
button { background: black; color: white; border: 0; border-radius; 5px; padding: 3px 15px; }
`)
document.adoptedStyleSheets = [cssss]

customElements.define("ex-ample", class extends CustomHTMLElement {
static css = "button:hover { background: darkgray; }"

constructor() {
super()
this.shadowRoot.innerHTML = `<button>in component</button>`
}
})

document.body.innerHTML = `
<button>in document</button>
<ex-ample></ex-ample>
`
深いネストで全部自動で広げたい
こういう構造で

<div class="a">
<div class="b1">message</div>
<div class="b2">
<div class="c1">message</div>
<div class="c2">
<div class="d1">message</div>
<div class="d2">
<div class="e1">message</div>
<div class="e2">
spread max
</div>
</div>
</div>
</div>
</div>

外側の a はサイドバーみたいなもので ウィンドウサイズに応じて高さが変わって 子孫要素の大きさは関係なく親に応じてサイズが変わる
そのときに一番深い e2 の div が下の隙間いっぱいに広がってほしい

高さ固定は無理だし JavaScript でリサイズごとに高さ指定もしたくない
となると CSS で全部 flexbox 化するしかなさそう

.a, .b2, .c2, .d2 {
display: flex;
flex-flow: column nowrap;
}

.b2, .c2, .d2, .e2 {
flex: 1 1 auto;
}

むやみに flexbox 増やすと思わぬ動きしたり困ること多いし もっと楽で良い方法ってないものかなー
e2 を広げる以外に b2, c2, d2 を flexbox にする必要ないんだけど
マウス乗せたときだけスクロールバーを出す
:hover で overflow を切り替えるだけ
スクロールバーデザインも変えてモバイル風味にしてみた

webkit-scrollbar 系には opacity 効かないし 背景を透明にしても裏側は見れない
-webkit-scrollbar-thumb の background は設定できるけど transition でアニメーションしない
JavaScript で徐々に背景色の alpha 値変えればいけそうだけど CSS で収めたかったのでやってない

<!doctype html>

<style>
* {
box-sizing: border-box;
}

.box {
width: 200px;
height: 300px;
border: 2px solid #c4cf8a;
overflow: hidden;
}

.box:hover {
overflow: overlay;
}

.box::-webkit-scrollbar {
width: 6px;
height: 6px;
}

.box::-webkit-scrollbar-thumb {
background: #aaa;
border-radius: 5px;
}
</style>

<div class="box"></div>

<script>
const b = document.querySelector(".box")
b.innerHTML += ("12345".repeat(10) + "<br/>").repeat(100)
</script>
初回表示時にアニメーションしてしまう
表示の ON/OFF で CSS でスライドアニメーションするつくり
クラスのトグルで表示を切り替え
初期状態は localStorage の値次第

初期状態が ON のときにクラスをつけても初回表示の場合はアニメーションせず初期状態として表示や非表示になってるはず
なのになぜかアニメーションする

調べてみたら 別の箇所の初期化の途中で offsetHeight を参照してた
これがあるとその時点でレイアウト計算が行われるので それ以降にクラスを切り替えてtransition が設定されたプロパティが変更されるとアニメーションする

しかたないので アニメーション OFF の状態でレイアウト計算させるようにした

elem.classList.remove("animation")
elem.getClientRects()
elem.classList.add("animation")

アニメーションが OFF の状態でクラス変更なのでアニメーションは起きない
その後 アニメーションを ON にしてもそれ以降に変化はないのでアニメーションしない

ページ準備完了後に body に ready クラスをつけて transition は 「.ready some-elem」 みたいなロード後にのみ有効になるようにしたほうがいいかも

くるくる回る loading アイコンの WebComponent
customElements.define(
"spin-loading",
class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" }).innerHTML = `
<style>
#loading {
display: block;
box-sizing: border-box;
width: var(--size, 100px);
height: var(--size, 100px);
border: 0 solid transparent;
border-bottom-width: calc(var(--size) / 2);
border-right: var(--width, 6px) solid var(--color, #d4ba00);
border-radius: 50%;
animation: loading var(--speed, 0.6s) linear 0s infinite;
}

@keyframes loading{
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
</style>
<div id="loading"></div>
`
}
}
)

使用例

<!doctype html>

<script src="loading.js"></script>

<style>
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
width: 100vw;
height: 100vh;
background: #333;
}
spin-loading{
--size: 100px;
--speed: 0.6s;
--width: 6px;
--color: #d4ba00;
}
</style>

<spin-loading></spin-loading>



以上の内容はhttps://let.blog.jp/tag/CSSより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14