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コードが将来的にも機能するかどうかは不透明です。

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属性は廃止済みであり指定すべきでないとされています。

私とIEとフィードバック(IE卒業式)2022年06月19日 12時28分

2022年6月16日に開催された「IE卒業式」というイベントで、「私とIEとフィードバック」という発表(5分間のライトニングトーク)をしてきました。以下に話した内容を掲載します。


私とIEとフィードバック

2022-06-16

nanto_vi (株式会社はてな)

Web開発者によるフィードバック

  • 標準準拠
  • 相互運用性の向上

➜ より開発しやすく

ブラウザベンダが不具合に気づくことを期待するという受身の立場から、積極的に熱心でいる(自らバグを報告する)ことへの移行は、信じられないほど多くの力をあなたにもたらします。

John Resig

[発話] ソフトウェアを作るうえでも使う上でもフィードバックは重要ですね。Webブラウザの場合、Web開発者からのフィードバックによって標準準拠の度合いが進んだり、ブラウザ間の相互運用性が向上したりして、Web開発者にとってはより開発しやすくなります。John Resigさん——jQueryを作った方です——も、バグが直るのを待つのではなく自らバグを報告するとめっちゃええことあるというようなことをおっしゃっています。

個人開発者によるIEへのフィードバック手段

go to http://131.107.85.110/msdn/bugreports/ to report issues

Internet Explorer 5.5 Preview

[発話] 個人がIEにフィードバックする手段は、IE 7の前後で大きく変わっています。IE 7以前はニュースグループ——掲示板やメーリングリストのようなものです——が中心でした。IE 7以降はMicrosoft Connectというサービスを使うようになっています。Microsoft Connectにはバグトラッキングシステムが備わっており、他人の登録したバグを検索したり、自分の登録したバグの状態を知れたりと、バグ報告者からすると使いやすくなっています。ちなみに、IE 5.5 Previewのときにはバグ報告用のURLがIPアドレス丸出しで、のどかな時代だったんだなというのを感じさせます。

IE 8 Betaへのフィードバック

開発者の支持のないブラウザは廃れるとの思いで、標準準拠路線を進めてきた

「ここが変わった! IE8 Beta2」(第13回 Admintech.jp勉強会)

  • 筆者もフィードバックに参加
    • Web Storageがオリジンごとではなくドメインごと
    • Web Storageのイベントが同期的
    • 文書間メッセージング(postMessage)のイベントが同期的

[発話] 私がIEにフィードバックするようになったのはIE 8 Betaが出たあたりからです。このころはFirefoxとSafariがIEを追い上げ、GoogleがChromeを発表し、Microsoftとしては非常に危機感を持っていたのではないかと思います。実際に、当時あったイベントでMicrosoftの方が、開発者の支持のないブラウザは廃れるとの思いで標準準拠路線を進めてきたというようなことをおっしゃっていました。このころはWeb Storage APIや文書間メッセージングが新機能としてもてはやされ、私も標準仕様とIEの実装との差異をいくつか報告しています。Web Storage API——localStorageなど——の範囲がオリジンではなくドメインである、すなわちhttpsのページで書き込んだデータをhttpのページで読み込めるというったことがありました。

フィードバックの返礼品

  • IE 8の工具セット
  • FirefoxとThunderbirdの傘

[発話] そうしたバグ報告をしているとMicrosoftからメールが来て、グッズを送るから住所を登録してくれと言われました。何かしらと思って住所を入力すると後日IE 8の工具セットが届きました。これでIEを直せということでしょうかね。こうしたノベルティの贈呈はMicrosoft以外のブラウザベンダもやっており、MozillaからはFirefoxとThunderbirdの傘をもらったことがあります。

フィードバックしよう!

[発話] 皆さんもどんどんフィードバックしていきましょう。個別のブラウザにフィードバックする以外にも、ブラウザ間の相互運用性を高めるためにweb-platform-testsというものがあります。web-platform-testsにコミットすると各ブラウザで実行され、場合によってはブラウザ側で挙動が変更されることもあります。フィードバックを通じてWebの将来を作っていきましょう。ありがとうございました。


以上が発表内容です。久々の発表、しかもリモートからの参加ということで緊張しましたが、無事終えられてよかったです。

イベント自体は懐かしい機能を思い出したりIEの功績を振り返ったりと、IEの存在感の大きさを改めて感じられるものでした。イベントを企画・運営してくださった方々に感謝します。(IEのケーキが思ったより大きくて、現地で食べてみたかったです。)

IE 6も登場した時点ではそこまで悪いものではなく(個人的にはまだCSS 2ではなくCSS 1なのかと思いましたが……)、開発者の落胆を招いた要因としては、その後5年もメジャーバージョンアップがなかったという部分も大きいかと思います。

発表するにあたって、発表内容で触れた以外にも以下のページを参考にしました。また、窓の杜およびInternet Archiveには当時の記事が多数残っており、調査の大きな助けとなりました。

HTML のフォームコントロール要素と label 要素の紐づけ2021年12月24日 21時11分

この記事は HTML アドベントカレンダーの 24 日目の分、兼 JavaScript アドベントカレンダーの 24 日目の分です。


HTML のフォームコントロール要素 (inputtextareaselectbutton 要素など) には、label 要素を使ってラベルを指定できます。ここでいうラベルとは、そのフォームコントロールに何を入力するか・そのフォームコントロールで何ができるのかの簡単な説明であり、人間が読んで理解できるようなフォームコントロールの名前です。

ある label 要素の子孫にフォームコントロール要素が存在すれば、その label 要素の内容が、そのフォームコントロール要素のラベルとなります。そうでない場合、label 要素の for 属性にフォームコントロール要素の ID (id 属性の値) を指定する必要があり、その label 要素の内容が、その ID を持つフォームコントロール要素のラベルとなります。

このフォームコントロール要素と label 要素との紐づきは JavaScript を使って参照できます。フォームコントロール要素オブジェクトの labels プロパティはそのフォームコントロール要素と紐づく label 要素の一覧 (NodeList オブジェクト) を返し、label 要素オブジェクト (HTMLLabelElement オブジェクト) の control プロパティはその label 要素に紐づくフォームコントロール要素を返します。

labels プロパティの名前が複数形なのは、ひとつのフォームコントロール要素に対して複数の label 要素を紐づけられるからですね。

<label id="query-label-1" for="query-field">キーワード</label>
<label id="query-label-2" for="query-field">URL</label>
<input id="query-field" type="search" name="q">
const label1 = document.getElementById('query-label-1');
const label2 = document.getElementById('query-label-2');
const field = document.getElementById('query-field');

console.assert(label1.control === field, 'control プロパティでフォームコントロールを参照できる (1)');
console.assert(label2.control === field, 'control プロパティでフォームコントロールを参照できる (2)');
console.assert(field.labels[0] === label1, 'labels プロパティで label 要素を参照できる (1)');
console.assert(field.labels[1] === label2, 'labels プロパティで label 要素を参照できる (2)');

あるフォームコントロールにおいて、labels プロパティの返す NodeList オブジェクトは常に同一です。紐づく label 要素に変更があれば、その NodeList オブジェクトの内容が動的に変化します。

const oldLabels = field.labels;
label1.remove();
const newLabels = field.labels;

console.assert(oldLabels === newLabels, 'labels プロパティの値は何度参照しても同一のオブジェクトである');
console.assert(oldLabels.length === 1, 'labels プロパティの値は動的に変化する');

ただし、実際のところひとつのフォームコントロール要素に複数の label 要素を紐づけるような場面はほとんどないと思います。

button 要素にも label 要素を紐づけられます。しかしながら、button 要素の場合は自身の内容がラベルとして扱われるので (<button type="submit">検索する</button> なら「検索する」がそのボタンのラベルになります)、実際のところ button 要素に label 要素を紐づけるような場面はほとんどないと思います。

ラベルは aria-label 属性aria-labelledby 属性を使って指定することもできます。

<form action="/search">
  <p>
    <input type="search" name="q" aria-label="キーワード">
    <button type="submit" aria-label="検索する">🔍</button>
  </p>
</form>

HTML のテキスト入力欄の入力値の一部を置換する2021年12月23日 23時48分

この記事は HTML アドベントカレンダーの 23 日目の分、兼 JavaScript アドベントカレンダーの 23 日目の分です。


HTML のテキスト入力欄 (<input type="text"> 要素や textarea 要素など) で選択範囲の文字列を置換したいとき、一昔前は JavaScript で以下のように書く必要がありました。

// 引数 field には HTMLInputElement オブジェクトまたは HTMLTextAreaElement オブジェクトを、
// 引数 newText には文字列を、それぞれ受け取る。
function replaceSelectionText(field, newText) {
  var start = field.selectionStart;
  var end = field.selectionEnd;
  var text = field.value;
  field.value = text.substring(0, start) + newText + text.substring(end);
  field.setSelectionRange(start, start + newText.length);
}

(さらに大昔は IE 向けに TextRange オブジェクトを使う必要がありました。)

今は HTML 標準で setRangeText メソッドが定義されているため、以下のように書けます。

function replaceSelectionText(field, newText) {
  field.setRangeText(newText);
}

setRangeText メソッドには置換する範囲を指定することもできます。テキスト入力欄 field の入力値が abcde のとき、以下のコードを実行すると入力値が axde になります。

field.setRangeText('x', 1, 3);

このとき、元の入力値において改行は \n に正規化され、1 文字として数えられます。また、「1 文字」というのが Unicode の符号位置ではなく UTF-16 の符号単位を表すことに注意が必要です。

置換する範囲を指定して setRangeText メソッドを呼び出したとき、選択範囲はメソッド呼び出しの前後であまり変化しないように調整されます。この挙動は、第 4 引数に selectstartendpreserve のいずれかの値を指定することで変更できます (省略時は preserve)。以下の例ではメソッド呼び出し後に部分文字列 x が選択されます。

field.setRangeText('x', 1, 3, 'select');