💬

#7 2021年2月にツイートしたHTML/CSS/JavaScriptのTipsまとめ

2021/04/09に公開

2021年2月にツイートしたHTML/CSS/JavaScriptのツイートまとめです。見出しをクリックするとツイート元に遷移するので、気に入ったらフォロー・ファボ・リツイートお願いします。

1. 最初の要素を選択する特殊なセレクタ

リストの li 要素のうち、1番目の li 要素を選択するときには :first-child を使うと思いますが、場合によっては間違った書き方となってしまいます。

ul > li:first-child { ... }

:first-child だと hidden 属性で非表示になっている要素にもスタイルが適用されてしまうため、本来は <li>2</li> を選択して欲しいのですが、<li hidden>1</li> が選択されます。

<ul>
  <li hidden>1</li>
  <li>2</li>
  <li hidden>3</li>
  <li hidden>4</li>
  <li>5</li>
  <li>6</li>
  <li hidden>7</li>
  <li>8</li>
  <li>9</li>
</ul>

:not([hidden]) ~ :not([hidden])hidden 属性をもたない要素以降の hidden 属性を持たない要素群で、hidden 属性をもたない2番目以降の要素すべてを選択するセレクタです。

ul > :not(:not([hidden]) ~ :not([hidden])) { ... }
IE Edge Firefox Chrome Safari Opera iOS Safari Android
- 88 84 88 9 74 9 88

それに、:not() がついているため、hidden 属性をもたない最初の要素である <li>2</li> が選択されます。

ul > :not([hidden]) ~ :not([hidden]) { ... }

逆に最初の要素以外を選択するには :not() を外せば大丈夫です。このセレクタならすべてのブラウザで使えるため、非対応ブラウザむけにCSSの上書きで対応することもできます。

2. ::target-text擬似要素

Chrome 89からText Fragmentsのスタイルを ::target-text で変更できます。

::target-text {
  background: lightcyan;
}

3. :-webkit-autofillが標準化へ

input 要素などのオートフィル(自動入力)時に適用される :-webkit-autofill が標準化され、Firefox 86から :autofill が実装されました。

input:-webkit-autofill {
  // WebKitブラウザ
}
input:autofill {
  // Firefox 86から実装
}

4. オートフィル時の背景色

Chromeではオートフィル(自動入力)でフォームに値が設定されると、背景色が薄い青色(昔は黄色)になります。

デザイン性の高いサイトではオートフィル時の背景色が目立ちすぎてしまいます。

box-shadowで隠す

box-shadow を使って内側の影で隠してしまう方法があります。この方法の欠点は背景が透明の場合には使えないことです。

input:-webkit-autofill {
  box-shadow: 0 0 0 999px #fff inset;
}

transition-delayで遅らせる

:-webkit-autofill 擬似クラスでオートフィル時に transition-delay に長い時間を指定して、実質背景色が表示されないようにする方法です。

input {
  background: #fff;
}
input:-webkit-autofill {
  transition-property: background-color;
  transition-delay: 9999s;
}

ちなみに、input 要素に background ではなく background-color: #fff; とするとなぜかこの方法は使えなくなります。ですが、input 要素に background だけ指定するケースは普通はなく、以下のように border プロパティと一緒に使うことがほとんどだと思います。

input {
  border: 1px solid #ced4da;
  background: #fff;
}
input:-webkit-autofill {
  transition-property: background-color;
  transition-delay: 9999s;
}

border プロパティと一緒に使えば、このテクニックは使えます。

5. clip-path: path()

モダンブラウザで clip-path プロパティに path() 関数が使えるようになりました。

<img src="https://picsum.photos/1280/720">
img {
  width: 250px;
  height: 250px;
  object-fit: cover;
  clip-path: path('M213.1,6.7c-32.4-14.4-73.7,0-88.1,30.6C110.6,4.9,67.5-9.5,36.9,6.7C2.8,22.9-13.4,62.4,13.5,110.9 C33.3,145.1,67.5,170.3,125,217c59.3-46.7,93.5-71.9,111.5-106.1C263.4,64.2,247.2,22.9,213.1,6.7z');
}
IE Edge Firefox Chrome Safari Opera iOS Safari Android
- 88 71 88 10 74 10 88

6. scroll-behaviorの注意点

CSSでスムーズスクロールを実装するには scroll-behavior: smooth; を使いますが、普通に指定するだけだと色んな問題が起きます。

Chromeでサイト内検索するときに矢印ボタンで移動していくと、その移動までもスムーズスクロールになってしまいます。

html:focus-within {
  scroll-behavior: smooth;
}

擬似クラス :focus-within は子孫要素の中に :focus 状態の要素があれば適用されます。それを利用して :focus 状態のときにスムーズスクロールされるようにします。

<a href="#bottom">下へ</a>

しかし、擬似クラス :focus-within を使った解決策だとページ内リンクをクリックしたときにChromeとFirefoxでスムーズスクロールしなくなってしまいます。

@keyframes smooth-scroll-1 {
  from, to { scroll-behavior: smooth; }
}
@keyframes smooth-scroll-2 {
  from, to { scroll-behavior: smooth; }
}
html {
  animation: smooth-scroll-1 1s;
}
html:focus-within {
  animation-name: smooth-scroll-2;
  scroll-behavior: smooth;
}

そこで、scroll-behavior: smooth; を指定した2つのアニメーションを用意し、:focus されたときにアニメーションを切り替えることで正しくスムーズスクロールされるようにしています。将来的にはサイト内検索の問題は解決されると思いますが、まだ時間がかかりそうです。

7. CSSハックを支える技術

https://zenn.dev/takamoso/articles/css-hack-tech

8. コンテナクエリ

ブラウザ幅のメディアクエリではなく、特定の要素の幅で場合分けできるコンテナクエリが検討中です。

https://github.com/w3c/csswg-drafts/issues/5796

9. フォームからフォーカスが外れたときの判定

focusout イベントだとタブキーで遷移したときにも focusout イベントが発生してしまいます。

<form>
  <input type="text">
  <input type="text">
  <input type="text">
  ...
</form>
const form = document.querySelector('form')

form.addEventListener('focusout', event => {
  // フォームからフォーカスが外れたとき
})

event.relatedTarget を活用すればタブキーでの遷移を除外できます。

const form = document.querySelector('form')

form.addEventListener('focusout', event => {
  if (form.contains(event.relatedTarget)) {
    return
  }

  // フォームからフォーカスが外れたとき
})

10. addEventListener()とAbortController()

addEventListener()AbortController() が使えるようになりました。これにより、ドラッグ&ドロップ実装のときのような removeEventListener() を複数記述する必要がなくなって見やすくなります。

従来は...
const element = document.querySelector('div')

// マウスのボタンを押し込んだとき
element.addEventListener('mousedown', event => {
  // マウスの左クリック以外は無視
  if (event.buttons !== 1) {
    return
  }

  const {offsetX, offsetY} = event

  // マウスを動かしているとき
  const onMousemove = event => {
    const {pageX, pageY} = event
    
    element.style.left = `${pageX - offsetX}px`
    element.style.top = `${pageY - offsetY}px`
  }

  // マウスのボタンを離したとき
  const onMouseup = event => {
    // mousedownするたびにイベントを追加しているので、ここで解除する
    window.removeEventListener('mousemove', onMousemove)
    window.removeEventListener('mouseup', onMouseup)
  }

  // elementにaddEventListenerすると、マウスを高速で動かしたときに動作しない
  window.addEventListener('mousemove', onMousemove)
  window.addEventListener('mouseup', onMouseup)
})
AbortController()を使うと...
const element = document.querySelector('div')

// マウスのボタンを押し込んだとき
element.addEventListener('mousedown', event => {
  // マウスの左クリック以外は無視
  if (event.buttons !== 1) {
    return
  }

  const {offsetX, offsetY} = event
  const controller = new AbortController()

  // マウスを動かしているとき
  const onMousemove = event => {
    const {pageX, pageY} = event
    
    element.style.left = `${pageX - offsetX}px`
    element.style.top = `${pageY - offsetY}px`
  }

  // マウスのボタンを離したとき
  const onMouseup = event => {
    controller.abort()
  }

  // elementにaddEventListenerすると、マウスを高速で動かしたときに動作しない
  // 第3引数のsignalにcontroller.signalを指定することで、
  // controller.abort()を呼び出すだけで両方のイベントを解除できる
  window.addEventListener('mousemove', onMousemove, {signal: controller.signal})
  window.addEventListener('mouseup', onMouseup, {signal: controller.signal})
})

11. Intl.ListFormat()

Intl.ListFormat() を使うと、配列を言語にあわせてフォーマットしてくれます。多言語サイトを作るときなどに便利ですね。

const list = ['A', 'B', 'C', 'D']

const format1 = new Intl.ListFormat('en', {style: 'long', type: 'conjunction'})
console.log(format1.format(list))  // => A, B, C, and D

const format2 = new Intl.ListFormat('en', {style: 'short', type: 'disjunction'})
console.log(format2.format(list))  // => A, B, C, or D

const format3 = new Intl.ListFormat('en', {style: 'narrow', type: 'unit'})
console.log(format3.format(list))  // => A B C D

第1引数には言語を指定し、第2引数にはオブジェクトでオプションを指定します。style には long/short/narrow を、type には conjunction/disjunction/unit を指定できます。

// A, B, C, and D
const format1 = new Intl.ListFormat('en', {style: 'long', type: 'conjunction'})

// A, B, C, or D
const format2 = new Intl.ListFormat('en', {style: 'long', type: 'disjunction'})

// A, B, C, D
const format3 = new Intl.ListFormat('en', {style: 'long', type: 'unit'})

// A, B, C, & D
const format4 = new Intl.ListFormat('en', {style: 'short', type: 'conjunction'})

// A, B, C, or D
const format5 = new Intl.ListFormat('en', {style: 'short', type: 'disjunction'})

// A, B, C, D
const format6 = new Intl.ListFormat('en', {style: 'short', type: 'unit'})

// A, B, C, D
const format7 = new Intl.ListFormat('en', {style: 'narrow', type: 'conjunction'})

// A, B, C, or D
const format8 = new Intl.ListFormat('en', {style: 'narrow', type: 'disjunction'})

// A B C D
const format9 = new Intl.ListFormat('en', {style: 'narrow', type: 'unit'})

オプションによる出力の違いを一覧にしてみました。

12. fetch()とpreload

fetch() 関数で preload したファイルを取得すると2度同じファイルがダウンロードされてしまいます。

<link rel="preload" href="./data.json" as="fetch">
fetch('./data.json')
  .then(data => data.json())
  .then(json => console.log(json))

2重ダウンロードを防ぐには fetch() 関数にオプションを指定します。

同一ドメインのとき

<link rel="preload" href="./data.json" as="fetch">
fetch('./data.json', {credentials: 'include', mode: 'no-cors'})
  .then(data => data.json())
  .then(json => console.log(json))

クロスドメインのとき

<link rel="preload" href="https://api.github.com/users/takamoso/repos" as="fetch" crossorigin>
fetch('https://api.github.com/users/takamoso/repos', {mode: 'cors'})
  .then(data => data.json())
  .then(json => console.log(json))

13. Pikaday

Pikadayは依存ライブラリなしの日付ピッカーライブラリです。

14. golden-layout

golden-layoutはコーディングエディタやPhotoshopのように1画面上で複数のウィンドウやタブを自由に移動できる機能を実装できるライブラリです。

15. a11y-dialog

a11y-dialogは軽量でアクセシブルなモーダル(ダイアログ)ライブラリです。

16. tippyjs

tippyjsは多機能なツールチップライブラリです。

17. redaxios

readaxiosはFetch APIを利用してaxiosをたったの800バイトで実装したライブラリです。

18. linkedom

linkedomはJSDOMのようなDOMパーサーライブラリです。

19. sql-formatter

sql-formatterはSQL文を見やすく整形してくれるライブラリです。

20. excellentexport

excellentexportはHTMLのテーブルをExcelまたはCSVファイルとして出力できるようにするライブラリです。

21. event-target-shim

event-target-shimEventTarget()Event() のPolyfillライブラリです。

22. Glassmorphism CSS Generator

Glassmorphism CSS Generatorは半透明でぼかしが入ったGlassmorphismのオンラインジェネレーターです。

23. Pattern Generator

Pattern Generatorはシームレスなパターンを生成できるオンラインジェネレーターです。

24. CodeCopy

CodeCopyはWebページ上のソースコードにコピーボタンを追加できるChrome/Firefox拡張機能です。あらかじめ登録されたドメイン以外にも、自分で追加できます。


#6 ← | → #8


今すぐ使えるCSSレシピブック

Discussion