Статьи

Как я создал кроссворд из чистого CSS

CSS кроссворд

Недавно я создал чистый кроссворд CSS, реализованный с использованием CSS-сетки, для работы которой не требуется JavaScript. Он довольно быстро приобрел большой интерес к CodePen. На момент написания статьи у него более 350 сердец и более 24 000 просмотров страниц!

Отличный учебник CSS Grid Garden вдохновил меня на создание чего-то с помощью функций Grid Layout. Я задавался вопросом, можно ли использовать эти функции при создании кроссворда — тогда я подумал, давайте попробуем создать все это без использования JavaScript.

Сборка доски / сетки

Итак, во-первых, давайте создадим саму доску!

В итоге я получил следующую базовую структуру с HTML-комментариями, чтобы показать, как будут работать различные разделы:

<div class="crossword-board-container"> <div class="crossword-board"> <!-- input elements go here. Uses CSS Grid as its layout --> <div class="crossword-board crossword-board--highlight crossword-board--highlight--across"> <!-- highlights for valid 'across' answers go here. Uses CSS Grid as its layout --> </div> <div class="crossword-board crossword-board--highlight crossword-board--highlight-down"> <!-- highlights for valid 'down' answers go here. Uses CSS Grid as its layout --> </div> <div class="crossword-board crossword-board--labels"> <!-- row and column number labels go here. Uses CSS Grid as its layout --> </div> <div class="crossword-clues"> <dl class="crossword-clues__list crossword-clues__list--across"> <!-- clues for all the 'across' words go here --> </dl> <dl class="crossword-clues__list crossword-clues__list--down"> <!-- clues for all the 'down' words go here --> </dl> </div> </div> </div> 

Это помещает наш основной скелет в место, чтобы мы могли добавить больше элементов и начать стилизацию.

Использование элементов формы для квадратов

Кроссворд, который я создаю, представляет собой сетку 13 × 13 с 44 пробелами, поэтому мне нужно создать 125 элементов input каждый со своим идентификатором в формате item{row number}-{column number} , то есть item4-12 . Вот как будет выглядеть сетка:

Пустой кроссворд

Каждый из входных данных будет иметь minlength и maxlength «1», чтобы имитировать поведение кроссворда (то есть одну букву на квадрат). Каждый вход также будет иметь required атрибут, так что будет использоваться проверка формы HTML5. Я использую все эти атрибуты HTML5, используя CSS.

Использование общего селектора братьев и сестер

Элементы ввода визуально разложены по группам (в точности как кроссворд). Каждая группа элементов ввода представляет слово в кроссворде. Если каждый из элементов в этой группе является допустимым (что можно проверить с помощью псевдоселектора :valid ), то мы можем использовать CSS для стилизации элемента, который появляется позже в DOM (с помощью расширенного селектора CSS, называемого общим селектором одноуровневого типа) это будет указывать, что слово является правильным.

Из-за того, как работают родственные селекторы и как работает CSS в целом, этот элемент должен появиться позже в DOM. CSS может только стилизовать элементы, которые идут после выбранного в данный момент элемента. Он не может смотреть назад в DOM (или вверх по дереву DOM) и стилизовать что-либо перед текущим элементом (на данный момент, по крайней мере, в любом случае).

Это означает, что я могу использовать псевдокласс :valid для стилизации допустимых элементов:

 .input:valid { border: 2px solid green; } .input:invalid { border: 2px solid red; } 

Позже для стилизации элемента в DOM, который является родственным элементом другого элемента, я могу использовать селектор ~ (тильда / общий брат), например, A ~ B Этот селектор выберет все элементы, которые соответствуют B, которые являются родственными элементами A и появятся после A в DOM. Например:

 #input1:valid ~ #input2:valid ~ #input3:valid ~ #input4:valid ~ #input5:valid ~ .valid-message { display: block; } 

С этим кодом, если все эти входные элементы являются действительными, будет отображаться элемент valid-message .

Общий селектор брата здесь чрезвычайно полезен. Чтобы кроссворд сработал, мне нужно было убедиться, что все выложено так, чтобы я мог воспользоваться преимуществами общего выбора родного брата.

Законченный пример кроссворда использует вышеописанную технику, начиная со строки 285. Я выделил ее в блоке кода ниже:

 #item1-1:valid ~ #item1-2:valid ~ #item1-3:valid ~ #item1-4:valid ~ #item1-5:valid ~ #item1-6:valid ~ .crossword-board--highlight .crossword-board__item-highlight--across-1 { opacity: 1; } 

Эта часть CSS гарантирует, что если все эти входные элементы допустимы, то непрозрачность элемента .crossword-board__item-highlight--across-1 будет изменена. .crossword-board--highlight — это один из всех элементов ввода, а .crossword-board__item-highlight--across-1 является дочерним элементом для .crossword-board--highlight чтобы его можно было выбирать с помощью CSS!

Указание правильных ответов

Каждый ответ на кроссворд (т. .crossword-board__item-highlight--across-{{clue number}} Группа входных элементов) имеет соответствующий элемент сетки «индикатор правильного ответа» ( .crossword-board__item-highlight--across-{{clue number}} ). Эти элементы сетки размещаются за входными элементами на оси z и скрываются с использованием opacity: 0 . Когда введено правильное слово, отображается элемент сетки индикатора правильного ответа, если изменить opacity до 1 , как показано в приведенном выше фрагменте селектора псевдокласса.

Кроссворд с неполным словом

Кроссворд с завершенным словом

Этот метод повторяется для каждой группы «слов» входных элементов. Таким образом, это означает, что нужно вручную создать каждое правило CSS для каждого из элементов ввода в группе слов и затем выбрать соответствующий элемент сетки индикатора правильного ответа. Как вы можете себе представить, это заставляет CSS быстро расти!

Таким образом, логический подход заключается в создании всех правил CSS, которые показывают / скрывают правильные элементы сетки индикатора ответа для всех горизонтальных (поперечных) подсказок ответов. Тогда вы бы сделали то же самое для ответов на вертикальные подсказки.

Проблемы Грид-системы

Если, как и я, вы пытаетесь использовать как можно меньше CSS, вы быстро поймете, что у вас не может быть перекрывающихся областей сетки в одной и той же системе сетки без необходимости явного объявления этого. Они могут сидеть только рядом друг с другом (1 поперек, а 1 внизу — квадрат в правом верхнем углу доски, и это невозможно при использовании одной сетки CSS для разметки всех элементов индикатора правильного ответа).

Решение состоит в том, чтобы обернуть каждый горизонтальный (поперек) элемент сетки индикатора правильного ответа в свою собственную систему координат, а каждый вертикальный (вниз) элемент сетки индикатора правильного ответа — в другой. Таким образом, я все еще могу использовать CSS для их выбора (используя общий селектор братьев и сестер), и они не будут мешать друг другу и разрушать компоновку сеток.

Элементы CSS Grid Layout действуют аналогично элементам inline-block. В основном это означает, что если вы укажете два элемента сетки, которые будут занимать одно и то же пространство, то второй элемент будет обтекать первый элемент и появляться после него в сетке.

В приведенном выше примере первый элемент сетки имеет семь столбцов и простирается от первого столбца до седьмого столбца. Второй элемент сетки должен начинаться с 4-го столбца и охватывать 9-й столбец. Сетка CSS не нравится, поэтому она переносит ее в следующую строку. Даже если вы укажете grid-row: 1/1 во втором элементе, это будет иметь приоритет, а затем переместится первый элемент сетки во второй ряд.

Как объяснялось выше, я избежал этого, имея несколько сеток для горизонтальных и вертикальных элементов. Этой ситуации можно избежать, указав диапазон строк и столбцов для каждого элемента, но я использовал вышеописанный метод, чтобы уменьшить количество CSS, а также иметь более поддерживаемую структуру HTML.

Проверка правильности ввода букв

Каждый входной элемент имеет атрибут pattern с регулярным выражением в качестве значения. Регулярное выражение соответствует заглавной или строчной букве для этого квадрата:

 <input id="item1-1" class="crossword-board__item" type="text" minlength="1" maxlength="1" pattern="^[sS]{1}$" required value=""> 

Это не было идеальным, потому что ответы в HTML. Я хотел скрыть ответы в CSS, но я не мог найти способ сделать это успешно. Я попробовал следующую технику:

 .input#item1-1[value="s"], .input#item1-1[value="S"] { /* do something... */ } 

Но это не сработает. Селектор атрибутов выберет элемент на основе того, что на самом деле находится внутри HTML, и не учитывает изменения в реальном времени. Поэтому мне пришлось прибегнуть к подходу :valid псевдокласса, подробно описанному выше, и, следовательно, (и досадно) разоблачить ответы в самом HTML.

Подчеркивая Подсказки на Hover

Все горизонтальные (поперечные) подсказки обернуты в div , как и вертикальные (нижние) подсказки. Эти обертывающие элементы div являются родственными элементами input в сетке кроссвордов. Это продемонстрировано в структуре HTML, указанной выше в предыдущем блоке кода. Это облегчает выбор правильной подсказки (подсказок) в зависимости от того, какой элемент ввода фокусируется / перемещается.

Кроссворд

Для этого каждому элементу input требуются стили :active :focus и :hover чтобы выделить соответствующий ключ, применяя цвет фона, когда пользователь взаимодействует с элементом input .

 #item1-1:active ~ .crossword-clues .crossword-clues__list-item--across-1, #item1-1:focus ~ .crossword-clues .crossword-clues__list-item--across-1, #item1-1:hover ~ .crossword-clues .crossword-clues__list-item--across-1 { background: #ffff74; } 

Нумерация улик

Числа для подсказок расположены с помощью шаблона CSS Grid. Вот пример HTML, сокращенно:

 <div class="crossword-board crossword-board--labels"> <span id="label-1" class="crossword-board__item-label crossword-board__item-label--1"> <span class="crossword-board__item-label-text">1</span></span> <span id="label-2" class="crossword-board__item-label crossword-board__item-label--2"> <span class="crossword-board__item-label-text">2</span></span> <!-- rest of the items here..... --> </div> 

Тогда CSS выглядит примерно так:

 .crossword-board__item-label--1 { grid-column: 1/1; } .crossword-board__item-label--2 { grid-column: 4/4; } /* etc... more items here... */ 

Каждый номер помещается в начальную позицию соответствующей ему группы элементов ввода (или слова). Затем число делается равным ширине и высоте 1 квадрата сетки, чтобы оно занимало как можно меньше места в сетке. Это может занять еще меньше места, если реализовать CSS Grid по-другому, но я решил сделать это таким образом.

Флажок «Проверить правильность квадратов»

В верхней части кроссворда вы увидите флажок «Проверить правильность квадратов». Это позволяет пользователю проверять правильность определенных букв, даже если данное слово неверно.

Кроссворд выделены правильные квадраты

Создание этого довольно красиво, так как это одно правило CSS, которое выделяет все действительные квадраты. Он использует флажок для выбора всех допустимых элементов ввода, которые идут после отмеченного флажка в DOM.

Вот код:

 #checkvaliditems:checked ~ .crossword-board-container .crossword-board__item:valid { background: #9aff67; } 

Вывод

Это охватывает все основные методы, используемые в демо. В качестве упражнения это показывает, как далеко зашёл CSS за последние годы. Есть множество функций, с которыми мы можем проявить творческий подход. Я, например, не могу дождаться, чтобы попробовать и расширить другие новые функции до предела!

Если вы хотите возиться с CSS из этой статьи, я поместил все примеры кода в коллекцию CodePen . Полный рабочий CSS кроссворд можно найти здесь .