スタイルシート クロスワード の説明
戻る


JavaScript を使わず,スタイルシートだけでクロスワードを作ってみます.
全くのお遊びです.これが何か実用の役に立つようなことは多分無いでしょう.スタイルシートは本来,動作を記述するものではありませんので,かなり力ずくで作っています.

画面
(これはキャプチャ画像です.これで遊ぶことはできません.)

使い方と仕組みについて説明します.


使い方

「問題選択」で問題を選びます.
文字を入力したいマスをクリックするとそのマスが選択されます.または,カギの一覧でカギをクリックすると,そのカギの番号のマスが選択されます.
画面下方にある文字ボードで入力したい文字をクリックすると,その文字が選択したマスに入力されます.
「消」をクリックすると,選択したマスに入力されている文字が消去されます.
マスの選択を解除したいときは,黒マスをクリックするか,画面の背景や見出しの部分など,特に機能が無いところをクリックすると解除できます.

マスにすべて文字を入力すると判定ボタンが有効になります.「判定」または「判定+誤り箇所表示」を押下すると,正解か不正解かが表示されます.
正解の判定の表示
不正解の判定の表示
「判定+誤り箇所表示」については,それに加えて,正しくない文字が入力されているマスが赤色で示されます.
誤り箇所の表示
「クリア」をクリックすると入力した文字がすべて消去されます.マスが選択されているときは選択も解除されます.
マスが選択されているときや文字が入力されているときは問題を変更することはできません.他の問題に変えたいときはクリアを行ってください.


問題の選択

問題を選択する処理のためにラジオ ボタンを使います.ラジオ ボタンは非表示にしておき,各ラジオ ボタンに対してラベル(LABEL 要素)を関連付けて,問題を選択するボタンを作っています.
ボタンをクリックすると対応するラジオ ボタンが :checked 状態になるので,問題のデータを格納するカスタム プロパティに,選択した問題のデータを設定します.


問題のデータの持ち方

選択した問題に応じて,各マスに対応するカスタム プロパティに以下のようなデータを持ちます.
  • 縦の語の番号
  • 横の語の番号
  • 縦のカギの文言
  • 横のカギの文言
  • マスの文字
語の番号とカギの文言は,語の先頭のマスのプロパティに格納します.縦の語と横の語で先頭のマスが同じになるものは,両方に同じ番号を格納します.

マスの文字は,各文字をこのように数値に対応させて格納します.
ア → 1, イ → 2, ウ → 3, …

マスの選択

マスを選択する処理のために,マスの数分のラジオ ボタンを使います.ラジオ ボタンは非表示にしておき,各ラジオ ボタンに対してラベルを関連付けて,それをマスに付けています.
マスをクリックすると対応するラジオ ボタンが :checked 状態になるので,そのマスを選択する処理を行います.
後述のように,マスには選択を解除するラベルも付けますが,白マス(問題の文字が設定されているマス)については,このラベルの方が前面になるようにするので,白マスをクリックすると,このラベルがクリックされます.

カギにも番号に対応するマスのラベルを付けています.カギをクリックすると,その番号のマスが選択されます.

その他に,マスの選択を解除するためのラジオ ボタンを 2 個使います.

ひとつは単に選択を解除するだけの処理のためのものです.ラジオ ボタンは非表示にしておき,ラジオ ボタンに対してラベルを関連付けて,それを各マスと画面全体の最背面に付けています.
ラベルがクリックされると,このラジオ ボタンがチェックされ,マスを選択している方のラジオ ボタンはチェックが外れるので,マスの選択は解除されます.
黒マス(問題の文字が設定されていないマス)については,このラベルの方が前面になるようにするので,黒マスをクリックするとマスの選択が解除されます.画面の背景をクリックしたときも同様に,マスの選択が解除されます.
また,見出しなど,特に機能が無いところは pointer‑events:none を設定して,マウス イベントが透過するようにしているので,そのようなところをクリックしたときも最背面に付けたラベルがクリックされ,マスの選択が解除されます.

もうひとつはクリア処理のためのものです.やはりラジオ ボタンは非表示にしておき,ラジオ ボタンに対してラベルを関連付けて,「クリア」ボタンを作っています.
「クリア」をクリックすると,このラジオ ボタンがチェックされ,マスの選択は解除されます.
クリア処理では他の処理も行うので,上記のものとは別のラジオ ボタンを使っています.


文字の入力

文字を入力する処理のために,文字の数分(「消」を含む)のボタン(TYPE 属性 BUTTON の INPUT 要素)を使います.ボタンは非表示にしておき,各ボタンに対してラベルを関連付けて,それを文字ボードの対応する位置に付けています.
各マスに対応するカスタム プロパティで,入力した文字に対応する数値を持つようにします.
文字を押下するとボタンが :active 状態になるので,選択されているマスのプロパティに,文字に対応する数値を設定します.たとえば「ア」の文字を押下したときは 1 を設定します.

これだけだと,ボタンが :active 状態でなくなるとプロパティの値が初期値に戻ってしまうので,:active 状態でなくなった後も値を保持しておくために transition の機能を流用します.
transition では,プロパティの設定値が変更されてから実際に値が変わり始めるまでのディレイを設定できます.
たとえば,カスタム プロパティ --a の値を保持しておきたい場合,transition でこのようにディレイを設定します.
transition:--a 0s 10000s;
そうすると,--a の設定が変わってもディレイで指定した時間が経過するまでは元の値が保持されます.ディレイに充分大きな値を指定することで設定した値を保持できます.
値を変更するときにはディレイをゼロにして,すぐに値が変わるようにします.
そのような方法で,ボタンが :active 状態でなくなった後も入力した文字に対応する数値を保持します.
ただし,実際には複数のマスのうちのひとつだけについてこの処理を行う必要があるので,ディレイの部分をカスタム プロパティで指定するようにして,該当のマスのディレイだけを変えるような処理にしています.

「クリア」に対応するラジオ ボタンの :checked 状態では transition を無効にして,プロパティの値を未入力の状態に戻しています.

カスタム プロパティに transition を使うために,後述のように @ルールの @property でプロパティを定義しています.


データの表示の仕方

マスの内容とカギの表示には ::before::after の疑似要素を使います.
マスには語の番号と入力した文字を表示します.語の番号は番号が設定されているマス(語の先頭のマス)に表示します.
カギの一覧には,語の番号が設定されているマスに対して,語の番号とカギの文言を表示します.

語の番号と入力した文字の表示には CSS カウンタの機能を使います.
counter-reset で語の番号や入力した文字に対応する数値をセットして counter() で表示します.つまり,CSS カウンタは単に数値を文字に変換する機能として使っています.
入力した文字については,このようなカウンター スタイルを使います.
@counter-style ans {
  system:cyclic;
  symbols:"ア" "イ" "ウ" "エ" "オ"
          "カ" "キ" "ク" "ケ" "コ" "ガ" "ギ" "グ" "ゲ" "ゴ"
          "サ" "シ" "ス" "セ" "ソ" "ザ" "ジ" "ズ" "ゼ" "ゾ"
          "タ" "チ" "ツ" "テ" "ト" "ダ" "ヂ" "ヅ" "デ" "ド"
          "ナ" "ニ" "ヌ" "ネ" "ノ"
          "ハ" "ヒ" "フ" "ヘ" "ホ" "バ" "ビ" "ブ" "ベ" "ボ" "パ" "ピ" "プ" "ペ" "ポ"
          "マ" "ミ" "ム" "メ" "モ"
          "ヤ" "ユ" "ヨ"
          "ラ" "リ" "ル" "レ" "ロ"
          "ワ" "ヲ" "ン" "ー" "";
}
これで入力した数値が文字として表示されます.
1 → ア, 2 → イ, 3 → ウ, …

解答の判定

解答の判定の処理のためにボタンを使います.ボタンは非表示にしておき,各ボタンに対してラベルを関連付けて,判定ボタンを作っています.
判定ボタンを押下するとボタンが :active 状態になるので,各マスについて,問題の文字の数値と入力した文字の数値を比較します.
すべてのマスについて両者が等しい場合に正解,等しくないマスがひとつでもあれば不正解を表示します.
また,「判定+誤り箇所表示」の場合は,それぞれのマスについて,両者が等しくない場合にマスの色を赤にします.


カスタム プロパティの定義

このプログラム(?)では,いくつかのカスタム プロパティを @ルールの @property で定義しています.
@property の定義は,たとえばこのように書きます.
@property --a {
  syntax:'<integer>';
  inherits:true;
  initial-value:0;
}
上述したように,カスタム プロパティの値を保持するために transition を使っています.カスタム プロパティに transition を使うためにはプロパティのデータ型を定義する必要があります.
@propertysyntax<integer> を指定することで,そのプロパティに transition を使えるようにしています.


条件分岐

スタイルシートでは,プログラミング言語のように if 文などで条件を判定して処理を分けるようなことができません.プロパティの設定はすべて式として書かなければなりません.また,直接数値の大小を比較するような機能もありません.
2024年に W3C の Working Draft で条件によってプロパティの値を設定する機能(if() 関数)が提案されましたが,その機能が実装されても,このプログラムのように計算の結果によってプロパティの値を変えることは,どうもできなそうです.
そのため,条件によって値を変えるところは,すべての条件の場合の値を計算しておいてから,そのうちのどれかが選ばれるような式を書くような形になります.

たとえば --x に,ある条件を満たす場合は --n1,満たさない場合は --n2 の値を設定するという場合,条件を満たす場合に 1,満たさない場合に 0 となるようなプロパティ --b を計算しておいて
--x:calc(var(--b) * var(--n1) + (1 - var(--b)) * var(--n2));
と書きます.
--b はプログラミング言語で言うブール変数に相当します.
--b の計算の仕方は,たとえば --v が整数を表すプロパティである場合,その値が 42 に等しいかどうかを表す --b はこのように計算できます.(小数の場合はもう少し面倒です.)
--w:calc(var(--v) - 42);
--b:calc(1 - min(var(--w) * var(--w), 1));
こんな風にも書けます.
--w:clamp(-1, var(--v) - 42, 1);
--b:calc(1 - var(--w) * var(--w));
abs() が実装されていれば,もう少し簡単に書けます.
--b:calc(1 - min(abs(var(--v) - 42), 1));
--b1--b2 を条件の真偽を表すプロパティ(1/0)とすると,論理演算は次のように計算できます.
論理積(AND)
  var(--b1) * var(--b2)
論理和(OR)
  min(var(--b1) + var(--b2), 1)
否定(NOT)
  1 - var(--b1)
このような考え方で,条件分岐的な処理を力業で書いています.


このおもちゃは Firefox 128 以上および Opera 87 以上用に作っていますが,Google Chrome 107 で動くことが確認できています.Google Chrome の他のバージョンでの動作は未確認です.


戻る