💬

#3 2020年10月にツイートしたHTML/CSS/JavaScriptのTipsまとめ

2020/11/04に公開

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

1. download属性

a 要素に download 属性をつけると、href 属性で指定した URL のファイルをダウンロードさせられます。

<a href="path/to/file.zip" download>ダウンロード</a>

ただし、href 属性に指定した URL が Web サイトと同一ドメインである必要があり、別ドメインの場合は通常のリンクと同じように遷移します。

2. SVGのフォーカス

button や a 要素内に svg 要素があるとき、IE ではタブキーによる移動で 2 度フォーカスが当たったり、スクリーンリーダーに 2 回読み上げられたりします。

<button>
  <svg focusable="false">...</svg>
</button>
<a href="#">
  <svg focusable="false">...</svg>
</a>

svg 要素に focusable 属性を指定しておくと、この問題を解決できます。

3. 見出しタグと<br>

見出しの装飾目的で <br> タグを使ってしまうとスクリーンリーダーに読み上げられる際、その前後で途切れた読まれ方になってしまいます。下記の例では、「Hello (一呼吸) World!」と読み上げられます。

<h2>Hello<br>World!</h2>

そこで、&NewLine; を使って改行文字を入れて、white-space: pre-line; の指定でそのまま改行が反映されるようにします。こうすることで、「Hello World!」と自然な読み上げ方になります。

<h2>Hello &NewLine; World!</h2>
h2 {
  white-space: pre-line;
}

&NewLine; でなくとも、ユニコードやアスキーコードで改行させることもできますが、この方が直感的でわかりやすいと思います。また、スマホだけ改行したいというような場合があると思います。

<h2>Hello &NewLine; World!</h2>
@media (max-width: 414px) {
  h2 {
    white-space: pre-line;
  }
}

そのときはメディアクエリを使って、特定の画面幅のときだけ pre-line 値にすれば良いです。

4. :any-link疑似クラス

:any-link は :link または :visited に一致するすべてのリンク要素にマッチします。

nav :any-link { ... }

// これと同じ
nav :link, nav :visited { ... }

対応していないブラウザには、postcss-pseudo-class-any-link を使えば :link と :visited に変換してくれます。

5. :focus-visible疑似クラス

:focus-visible を使うとキーボードでフォーカスされたときを判定できます。:focus { outline: 0} としてしまうと、キーボードで移動したときにどこにフォーカスが当たっているのかわからないのでダメですが、:focus-visible を使うことでキーボード以外のときだけフォーカスリングを消せるようになります。

:focus {
  // フォーカスされたとき
}
:focus-visible {
  // キーボードを使ってフォーカスされたとき
}
:focus:not(:focus-visible) {
  // キーボード以外(マウスやタッチなど)でフォーカスされたとき
  // フォーカスリングを消す
  outline: 0;
}

まだまだ対応しているブラウザは少ないため、what-input を使って対応すると良いです。

[data-whatinput="mouse"] :focus,
[data-whatinput="touch"] :focus {
  // キーボード以外(マウスやタッチなど)でフォーカスされたとき
  // フォーカスリングを消す
  outline: 0;
}

6. @supportsとselector()関数

@supports で selector() 関数を使うと、セレクタのサポート状況を判定でき、下記の場合は :is セレクタの判定をしています。

@supports selector(:is(.a, .b)) {
  // :isセレクタに対応しているとき
  .selector {
    property: value;
  }
}
@supports not selector(:is(.a, .b)) {
  // :isセレクタに対応していないとき
  .selector {
    property: value;
  }
}

対応していないブラウザもあるため、別の構文を使ってセレクタの判定を行うこともできます。

_:is(.a, .b), .selector {
  // :isセレクタに対応しているとき
  property: value;
}

CSS のセレクタがパースされるとき、そのブラウザで実装されていないセレクタが使われていると、カンマで区切られた後の .selector のスタイルは無視されます。

7. 未満のメディアクエリ

780px 未満とするときに 1px 引いた (max-width: 779px) とするのは良くないです。

// NG
@media (max-width: 779px) {
  // 780px未満?
}

// OK
@media not all and (min-width: 780px) {
  // 780px未満
}

not 演算子を使ってクエリを反転させてやれば良いです。理由など詳細については拙著『今すぐ使えるCSSレシピブック』に詳しく書いてあります。

8. CSS変数のアニメーション

@property で定義された CSS 変数を transition プロパティで使ってアニメーションさせるテクニックです。

CodePenのURLが不正です
<div></div>
@property --num {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
div {
  transition: --num 5s;
  counter-set: num var(--num);
  font: 800 40px system-ui;
}
div::after {
  content: counter(num);
}
div:hover {
  --num: 100;
}

9. line-height: 1;

Chrome では input 要素に line-height: 1; を指定しても無視され、normal 値と同じになってしまうので注意が必要です。

<input type="text" value="Text">
<input type="text" value="Text">
input:nth-child(1) {
  line-height: 1;
}
input:nth-child(2) {
  line-height: 1.02;
}

試しに 1.02 を指定してみると、無視されずに描画されていることがわかります。

10. オーバーレイテキスト

背景画像の上に黒の半透明レイヤーをのせたデザインを実装するテクニックです。

<div class="overlay-text">オーバーレイなテキスト</div>
.overlay-text {
  padding: 6em 1em;
  color: #fff;
  text-align: center;
  font-size: 1.2em;
  background-image: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, .5)),
                    url('https://picsum.photos/1280/720');
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

background-image プロパティでは先に記述した方が前面レイヤーとして描画されます。この場合は、黒の半透明レイヤーを linear-gradient() で表現しています。

疑似要素を使ってもいいのですが、他の表現で疑似要素を使う場合があるのであえて linear-gradient() を使っています。

11. 縁取り文字

Web サイトに縁取り文字を入れるなら画像よりもインライン SVG を使うと変更に強いので便利です。

<svg viewBox="0 0 200 100">
  <text
    x="50%"
    y="50%"
    text-anchor="middle"
    dominant-baseline="central"
    fill="#fff"
    stroke="#1699cb"
    stroke-width="7"
    stroke-linejoin="round"
    stroke-linecap="round"
    font-weight="bold"
    paint-order="stroke"
  >てきすと</text>
</svg>

通常 fill → stroke の順に描画されるため、線を太くすると文字に被ってしまいますが、paint-order="stroke" によって stroke を背面に描画させられます。

<svg viewBox="0 0 200 100">
  <use
    xlink:href="#text"
    fill="none"
    stroke="#1699cb"
    stroke-width="7"
    stroke-linejoin="round"
    stroke-linecap="round"/>
  <text
    id="text"
    x="50%"
    y="50%"
    text-anchor="middle"
    dominant-baseline="central"
    fill="#fff"
    font-weight="bold"
  >てきすと</text>
</svg>

paint-order に対応していない IE などでも実装したい場合は、use 要素を使ってテキスト部分を複製すれば良いです。

12. Masonryレイアウト

CSS Grid Layout Module Level 3 で CSS Grid を使った Masonry レイアウトを実装できる仕様がまとまったようです。

<ol>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
<ol>
ol {
  display: inline-grid;
  grid: masonry / repeat(3, 2ch);
  border: 1px solid;
  masonry-auto-flow: next;
}
li {
  background: silver;
}
li:nth-child(2n+1) {
  background: pink;
  height: 4em;
}

13. CSSオンリーな油絵の肖像画

CSSだけで肖像画を作っているすごい人がいます。他の作品もあるので GitHub を見てみると面白いかもしれません。

14. 美しいtransformの作例

立体的なtransformの作例がたくさん見られます。

15. 配列の空値とカンマ

JavaScript でデータテーブル構造などを配列で実装するとき、特に指定する値がない場合は空値(カンマだけ)をよく使います。

const array = [, , , , 1, 2]
console.log(array.length)  // => 6

ただし、末尾のカンマは要素数に影響がないため注意してください。

const array = [, , , , 1, 2,]
console.log(array.length)  // => 6

16. 連番の配列

引数に最初と最後の値を指定すると、連番の配列を生成してくれる関数です。

const generateArray = (start, end) => [...Array(end + 1).keys()].slice(start)

console.log(generateArray(1, 5))
// => [1, 2, 3, 4, 5]

17. 値の交換

2 つの変数の値を交換するときは分割代入を使うと楽です。

let a = 1
let b = 2

[a, b] = [b, a]
// => a = 2
// => b = 1

18. 桁の区切り文字

数値の桁の区切りに _(Numeric Separators)が使えます。

const number = 1_234_567
// => 1,234,567

19. requestAnimationFrame()とフレームレート

requestAnimationFrame() は必ずしも 60fps 出るとは限らないので注意が必要です。iOS の低電力モードが ON になっているときは fps が落ちてかなりかくついて見えます。

また、クロスドメイン iframe 内で requestAnimationFrame() を使ったときも、iOS ではフレームレートが落ちます。下記のサンプルを iOS Safari で見てみてください。

上が 60fps のアニメーションで下が意図的に 30fps にしたアニメーションです。iOS Safari だと 60fps ではなく、もっさりして見えると思います。しかし、iframe 内を一度タップすると 60fps で描画されるようになります。

requestAnimationFrame() は animejs などほとんどのアニメーションライブラリで使われています。あくまでリクエストするだけで 60fps を保証するものではないので注意してください。

20. iOSの100vhバグ

iOS の 100vh バグを解決する記事を見て、少し負荷軽減してみました。

<div class="hero"></div>
.hero {
  min-height: 100vh;
  min-height: calc(var(--vh, 1vh) * 100);
}
let vw = window.innerWidth

// vhを設定する関数
const setViewportHeight = () => {
  if (vw === window.innerWidth) {
    // 画面の向きが変わらないときは無視
    // アドレスバーの開閉でscrollイベントが発生するため
    return
  }

  const vh = window.innerHeight * .01
  document.documentElement.style.setProperty('--vh', `${vh}px`)
  vw = window.innerWidth
}

// resizeイベント負荷軽減
const resizeEnd = (fn, time = 30) => {
  let timeout

  return (...args) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => fn.apply(this, args), time)
  }
}

// 画面サイズが変わったとき
window.addEventListener('resize', resizeEnd(setViewportHeight))

// ページ初回読み込み時
setViewportHeight()

requestAnimationFrame() を使ってもいいかもしれないです。

21. dragmove.js

dragmove.js は超軽量なドラッグ&ドロップを実装できるライブラリです。

22. detect-gpu

detect-gpu は UserAgent の GPU 版のようなライブラリです。

23. SheetJS

SheetJS は Node.js で Excel ファイルの編集や更新ができるライブラリです。

24. data-picker

date-picker はアクセシブルな日付選択 UI ライブラリです。

25. importabular

importabular は簡易 spreadsheet を実装できるライブラリです。

26. Print.js

Print.js は PDF ファイルを新規タブで開くことなく、その Web サイト内で PDF の印刷画面を表示できるライブラリです。

27. ZzFXM

ZzFXM は音符と楽器データのパターンからステレオミュージックトラックを生成できるライブラリです。

28. Layoutit!

Layoutit! は直感的な操作で CSS Grid を生成できるオンラインジェネレーターです。

29. Blob generator

Blob generator はいい感じの Blob を生成してくれるオンラインジェネレーターです。


#2 ← | → #4


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

Discussion