ℹ Это болтливый пост, описывающий мой пошаговый процесс разработки. Вы можете перейти к конечному результату здесь.
В начале этого года один из еженедельных вызовов CodePen был посвящен стилям списков. Если вы еще этого не сделали, я настоятельно рекомендую подписаться на еженедельные вызовы Codepen. Они отлично вдохновляют на небольшие упражнения по разработке фронтенда.
Вернемся к стилям списков — я сразу почувствовал вдохновение написать что-то легкомысленное и забавное. Я решил заменить традиционные числа упорядоченного списка изображениями, которые эзотерически представляют числа. В частности, я решил использовать покемонов в порядке покедекса.
Да, я фанат покемонов, но и многие разработчики в сообществе тоже. Есть много замечательных ресурсов по покемонам для побочных проектов разработчиков, таких как PokéAPI. Для этого небольшого проекта я выбрал изображения спрайтов PokéSprite в качестве пули упорядоченного списка.
Как я его создал
Шаг 1: Инкрементирование чисел в правилах CSS
Я начал абстрактно думать о том, что мне нужно сделать в CSS. Замена одного маркера номера списка изображением потребовала бы следующего CSS:
ol li::marker {
content: url("image_url_here");
}
Для того чтобы для каждого элемента списка было свое изображение, мне потребовалось отдельное правило для каждого дочернего элемента с использованием псевдокласса CSS nth-of-type.
ol li:nth-of-type(1)::marker {
content: url("image_001");
}
ol li:nth-of-type(2)::marker {
content: url("image_002");
}
ol li:nth-of-type(3)::marker {
content: url("image_003");
}
/* etc etc */
Копировать и вставлять это правило для каждого элемента списка быстро стало бы утомительно. Сейчас существует почти 1 000 покемонов. Ни в коем случае: Мне нужно было программное решение.
На данном этапе я хотел оформить это с помощью ванильного CSS, если это возможно, поэтому я начал играть с CSS-функцией counter(), чтобы программно генерировать числа. Что-то вроде этого:
counter-increment: idx;
ol li:nth-of-type(idx)::marker {
content: url("image_{idx}"); /* This is invalid 😡 */
}
Но после нескольких попыток и яростного гугления на тему того, как вставить значение счетчика в URL изображения, я нашел вот такой вопрос (без нужды не проголосованный) на Stack Overflow:
Могу ли я вставить счетчик css в url контента?
Ответ — нет. Облом. 😞
С другой стороны, ответ на этот вопрос Stack Overflow дал решение: SASS @for loop.
С его помощью я подготовил CSS-проводку для URL-адресов изображений покемонов:
ol li {
@for $i from 1 through 908 {
&:nth-of-type(#{$i})::marker {
content: url("#{$i}.png");
}
}
}
Шаг 2: Подготовка файлов изображений
Теперь не хватало только самих файлов изображений покемонов. Я загрузил эту коллекцию спрайтов с сайта PokéSprites и быстро заметил, что сделал неверное предположение: файлы были названы по имени покемона, а не по его номеру dex, например, bulbasaur.png
.
Но не стоит беспокоиться, PokéSprite также поставляется со структурированным файлом данных JSON, через который я мог сопоставить имя файла с номером dex. Я начал писать скрипт, который мог бы переименовывать сотни файлов за меня. Я не специалист по Node, поэтому после долгого гугления на тему Node FS я пришел к следующему:
// extractOrderedListOfPokemon.mjs
import * as input from './pokemon.json';
import * as fs from 'fs';
const dirPath = './by-nat-dex-number';
const data = input.default;
const filesToDelete = [];
const errCallback = err => {
if (err) {
console.error('ERROR: ' + err);
}
}
const dir = await fs.promises.opendir(dirPath);
for await (const dirent of dir) {
const filename = dirent.name;
const pokemonSlug = filename.replace(".png", "");
const dexNumber = Object.values(data).find(pokemon => pokemon.slug.eng === pokemonSlug)?.idx;
if (dexNumber) {
fs.rename(`${dirPath}/${filename}`, `${dirPath}/${dexNumber}.png`, errCallback);
} else {
filesToDelete.push(`${dirPath}/${filename}`);
}
}
filesToDelete.forEach(file => {
fs.unlink(file, errCallback);
});
Я запустил это как сценарий Node в терминале. Узнав, что мне нужна опция --experimental-json-modules
для работы импорта .json
, я получил нужные мне файлы с именами 001.png
, 002.png
и т.д., вплоть до 908.png
. Я загрузил их все в ведро AWS S3 для хостинга.
Шаг 3: Функция SASS zerofill
Все, что мне осталось сделать, это вставить URL-адреса изображений в код следующим образом:
ol li {
@for $i from 1 through 908 {
&:nth-of-type(#{$i})::marker {
content: url("https://my-s3-bucket-domain.com/#{$i}.png");
}
}
}
за исключением:
Упс, мои URL-адреса изображений заполнены нулями, или нулями в начале, чтобы каждое имя файла имело одинаковую длину символов. Мой код CSS ищет 1.png
, когда ему нужен 001.png
.
И снова решение нашлось на Stack Overflow, и я адаптировал свой код:
@function zerofill($i) {
@return #{str-slice("000", 0, 3 - str-length(#{$i}))}#{$i};
}
ol li {
@for $i from 1 through 908 {
&:nth-of-type(#{$i})::marker {
$i: zerofill($i);
content: url("https://my-s3-bucket-domain.com/#{$i}.png");
}
}
}
Результат
И та-да, 🎉 мой упорядоченный список покемонов!
⚠ Если вы пользователь Safari или iOS, к сожалению, вы не увидите пули покемонов. На момент написания статьи Safari имеет ограниченную поддержку псевдоэлемента ::marker
. Более подробную информацию вы можете найти в билете об ошибке.