Правильная постановка задачи это половина ее решения

Часто можно услышать: «правильная постановка задачи — это половина ее решения». И это действительно так, а во многих случаях — и больше половины.

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

С другой стороны, «расплывчатая» постановка задачи дает возможность людям с резиновой совестью либо «сэкономить» на самой постановке задачи (сама правильная постановка задачи тоже может стоить солидных денег и временных затрат), переложив это на человека, нанятого только для решения задачи. Либо «расплывчатая» постановка задачи позволяет (теперь уже со стороны исполнителя) «сэкономить» материалы или время на выполнение задачи, положив разницу в свой карман.

Вообще, я заметил, что в учебниках, на экзаменах в вузы, на соревнованиях по решению задач часто очень плохая постановка задач: авторы задач позволяют себе расплывчатость в формулировках, опускают важные детали и так далее. Правильная (не оставляющая разночтений и не позволяющая домыслов) постановка задач есть только в начальной школе (там детей еще жалеют, делают «скидку на возраст») и еще немного изучается теоретически в вузе.

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

В вузах, кстати, до введения ЕГЭ «расплывчатая» постановка задач на вступительных экзаменах служила инструментом для зарабатывания денег: абитуриенты, прошедшие платные подготовительные курсы, легко решали задачи на экзаменах, потому что «расплывчатые» формулировки задач им разъяснили на подготовительных курсах. В результате абитуриенты, не прошедшие платные подготовительные курсы, чаще валились на вступительных экзаменах в вуз.

Люди, рассуждавшие логически, могли налететь на эти грабли. Ведь, по идее, рассуждая логически, абитуриент, закончивший школу с хорошими оценками, должен иметь достаточные знания для успешной сдачи вступительных экзаменов в вуз. На деле это было не так именно из-за различных «расплывчатых» формулировок в задачах.

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

VLDL, оплата монетами

На «Youtube» есть канал очень популярной англоязычной группы «VLDL» (расшифровывается как «Viva La Dirt League»), снимающей юмористические скетчи:

https://www.youtube.com/channel/UCchBatdUMZoMfJ3rIzgV84g
https://www.vivaladirtleague.com

Кстати, рекомендую, у них очень забавные ролики!

На «Youtube» также есть множество каналов (я знаю не меньше десятка), которые занимаются переводом скетчей «VLDL» на русский язык. Недавно на одном из таких каналов я посмотрел скетч «Оплата монетами» (в оригинале «Paying with coins»):

https://www.youtube.com/watch?v=1GHtPaq_IUc (перевод на русский)
https://www.youtube.com/watch?v=tTx-BaBuZ6w (оригинал)

Там покупатель в магазине решил потроллить продавца и оплатить покупку наличными в монетах на сумму в 150 долларов. Вроде как такую кучу монет долго считать, и поэтому продавец в шоке.



В комментариях зашел разговор о том, сколько там монет и сколько времени понадобится, чтобы их пересчитать.

В ролике покупатель дополнительно к куче монет издевательски передает продавцу еще 15 монет как 1 процент от суммы оплаты в качестве чаевых.

На вид вся куча монет состоит из одинаковых монет. 1 процент от суммы оплаты составляет 1,5 доллара или 150 центов. Если 150 центов состоят из 15 монет, значит номинал одной монеты — десять центов. То есть вся куча монет состоит из десятицентовиков.

Таким образом, в куче одинаковых монет из ролика на сумму в 150 долларов должно содержаться 1500 монет номиналом в десять центов. Если считать вручную и на подсчет каждой монеты тратить 1-2 секунды, то подсчитать 1500 монет получится за 25-50 минут. Если же у продавца есть весы, то можно взвесить сразу все монеты и разделить на вес одной монеты. С весами всё прошло бы быстро, но, видимо, у продавца нет весов.

Пока я смотрел ролик, у меня закрались сомнения, что показанные в ролике монеты являются десятицентовиками. В США десятицентовики называют коротко «даймами» («dime»). Сейчас (с 1946 года) их делают из медно-никелевого сплава, которому никель придаёт серебряный цвет:

https://en.wikipedia.org/wiki/Dime_(United_States_coin)

А монеты в ролике — золотого цвета.

Позже мне напомнили, что доллары и центы бывают не только американские (США). Ребята из группы «VLDL» живут в Новой Зеландии, а там у них свои собственные, новозеландские, доллары и центы.

Новозеландский десятицентовик современного образца изготавливают с 2006 года. Он сделан из стали, покрытой медью. Медное покрытие придает монете золотой цвет.

https://en.wikipedia.org/wiki/Coins_of_the_New_Zealand_dollar
https://en.wikipedia.org/wiki/New_Zealand_ten-cent_coin

JavaScript: тонкости работы с событиями мыши

В процессе чтения подраздела 3.2 «Движение мыши: mouseover/out, mouseenter/leave» второй части учебника по JavaScript я экспериментировал с браузером и сделал несколько выводов, которыми можно дополнить указанный подраздел. Эта информация может пригодиться при решении примеров к этому подразделу.

Я экспериментировал в браузере «Microsoft Edge» на движке «Chromium». В экспериментах я рассматривал не все события мыши, а только mouseover (возникает при заходе курсора мыши на HTML-элемент), mouseout (возникает при уходе курсора мыши с HTML-элемента) и mousemove (возникает при движении курсора мыши над HTML-элементом).

* * *

Вообще правила работы браузера с событиями мыши определены в спецификации, которая сейчас называется «UI Events». Аббревиатура «UI» расшифровывается как «user interaction» (по-русски «взаимодействие с пользователем»). «UI Events» по-русски означает «события при взаимодействии с пользователем» (имеется в виду взаимодействие браузера с пользователем). Эту спецификацию можно почитать по следующим ссылкам:

1. https://www.w3.org/TR/uievents/
2. https://w3c.github.io/uievents/

По первой ссылке сейчас находится спецификация «UI Events» в варианте «W3C Working Draft» (по-русски «рабочий проект», это один из этапов разработки рекомендации (эквивалент стандарта) от организации «W3C», разрабатывающей стандарты для Всемирной паутины) от 30 мая 2019 года.

По второй ссылке сейчас находится спецификация «UI Events» в варианте «Editor’s Draft» от 20 июля 2021 года. «Editor’s Draft» — это, как я понимаю, свежайший снимок спецификации от людей («редакторов»), которые в данный момент занимаются ее дальнейшей разработкой.

В этой спецификации генерация рассматриваемых событий не расписана достаточно подробно. Многие моменты оставлены на усмотрение разработчиков браузеров. Поэтому некоторые моменты работы с событиями мыши нельзя узнать из справочников и учебников, их приходится узнавать через эксперименты.

* * *

В обсуждаемом подразделе учебника меня особенно заинтересовал отдел «Пропуск элементов».

В теории, когда пользователь водит мышью по HTML-странице в области просмотра браузера, генерируются три события, перечисленные выше, которые можно обработать в скрипте на языке JavaScript. Рассматриваемые события генерируются при проведении курсором мыши над конкретным HTML-элементом в порядке mouseover-mousemove-mouseout. При этом события mouseover и mouseout генерируются по одному на HTML-элемент, а событий mousemove над одним HTML-элементом между событиями mouseover и mouseout может быть множество.

Пользователь может водить мышью по HTML-странице с разной скоростью. В учебнике рассказано, что при достаточно высокой скорости проведения указателя мыши над HTML-элементом соответствующие события могут не сгенерироваться (произойдет «пропуск» этого HTML-элемента). В учебнике сказано, что HTML-элемент может быть пропущен только целиком. То есть, если сгенерировалось событие mouseover, то после этого для данного HTML-элемента обязательно будет сгенерировано и событие mouseout.

Я бы добавил еще к этому, что (судя по моим экспериментам):

Вывод 1. Браузер может либо полностью «пропустить» HTML-элемент, либо, как минимум, сгенерировать группу из трех событий mouseover-mousemove-mouseout (каждое из этих событий по одному, событие mousemove может быть в этом случае либо одно, либо их может быть множество). То есть у меня не получилось увидеть ситуацию, когда после захода и ухода указателя мыши с HTML-элемента сгенерировалась бы только пара событий mouseover-mouseout или mouseover-mousemove или mousemove-mouseout.

Следующий вывод может показаться очевидным, но всё же:

Вывод 2. «Пропуск» HTML-элемента более вероятен, если этот элемент маленький или тонкий. Чем больше (толще) HTML-элемент, тем менее вероятен его «пропуск».

Я замерил время между генерацией трех рассматриваемых событий на одном HTML-элементе. Для этого я применил метод Date.now(). Про этот способ можно прочитать в подразделе 5.11 «Дата и время» первой части обсуждаемого учебника.

Вывод 3. Время между рассматриваемыми событиями может быть от нуля миллисекунд (минимальное) и больше. Особенно часто промежуток в ноль миллисекунд бывает между событием mouseover и первым событием mousemove над одним и тем же HTML-элементом. Но промежуток в ноль миллисекунд между mouseover и первым mousemove бывает не всегда.

В учебнике сказано, что хоть событие mousemove и генерируется при движении курсора мыши, но это не означает, что оно генерируется при прохождении каждого пикселя.

Еще в учебнике сказано, что свойство relatedTarget объекта с информацией о произошедшем событии для трех обсуждаемых событий мыши может быть равно значению null, когда курсор мыши резко перемещается из-за пределов области просмотра браузера на HTML-элемент (для события mouseover) или, наоборот, резко перемещается с HTML-элемента за пределы области просмотра браузера (для события mouseout).

Я бы тут еще добавил, что:

Вывод 4. Для события mousemove свойство relatedTarget объекта с информацией о произошедшем событии всегда равно значению null (об этом сказано в вышеуказанной спецификации).

Я проанализировал координаты указателя курсора мыши в момент генерации событий mouseover и mouseout. Как мне казалось, в момент события захода указателя мыши на HTML-элемент координаты указателя должны быть равны координатам пикселя, находящегося в самом внешнем ряду пикселей границы (border) HTML-элемента. А в момент события ухода указателя мыши с HTML-элемента координаты указателя, как я думал, должны быть равны координатам пикселя, тоже находящегося в самом внешнем ряду пикселей границы (border) HTML-элемента. Но это представление оказалось неверным.

Вывод 5. При событии mouseover координаты указателя мыши равны координатам пикселя, либо находящегося в самом внешнем ряду пикселей границы HTML-элемента, либо находящегося несколько в глубине HTML-элемента. Чем больше скорость движения указателя мыши, тем глубже внутри HTML-элемента находится рассматриваемый пиксель. Иллюстрация:



Я сделал снимок области просмотра браузера и увеличил его в 4 раза в графическом редакторе, чтобы можно было видеть ситуацию попиксельно. На иллюстрации видно кусочек (левый верхний угол) HTML-элемента div. У него красная граница (border) толщиной в 15 пикселей, а клиентская часть этого HTML-документа (внутренние отступы padding плюс содержимое HTML-элемента) выкрашена в серый цвет. Черной пиксельной линией я изобразил движение указателя мыши (серой стрелкой показано направление движения указателя мыши, сверху вниз, на HTML-элемент). Желтым цветом отмечены пиксели, в которых может быть сгенерировано событие mouseover в зависимости от скорости движения указателя мыши.

Вывод 6. При событии mouseout координаты указателя мыши равны координатам пикселя, либо находящегося в самом внешнем ряду пикселей границы HTML-элемента, либо уже находящегося на каком-то расстоянии за пределами HTML-элемента. Иллюстрация:



Эта иллюстрация сделана по тому же принципу, что и предыдущая. На ней видно кусочек (левый нижний угол) HTML-элемента div. Черной пиксельной линией изображено движение указателя мыши (белой стрелкой показано направление движения указателя мыши, сверху вниз, из HTML-элемента). Желтым цветом отмечены пиксели, в которых может быть сгенерировано событие mouseout в зависимости от скорости движения указателя мыши.

Можно сказать, что в данном случае у браузера как-будто есть некая инерция, события несколько «запаздывают», из-за чего функция, обрабатывающая событие, получает несколько смещенные координаты.

JavaScript: подсказки к HTML-элементам

Решил задачу «Улучшенная подсказка» к подразделу 3.2 «Движение мыши: mouseover/out, mouseenter/leave» второй части учебника по JavaScript.

Как обычно, дана HTML-страница, код которой можно посмотреть в песочнице. На этой HTML-странице некоторые HTML-элементы содержат пользовательский атрибут data-tooltip (английское слово «tooltip» переводится на русский как «подсказка» или «всплывающая подсказка»; в скрипте я буду использовать английское слово «tip», одно из значений которого при переводе на русский тоже «подсказка»). В этом атрибуте может храниться либо простой текст, либо текст с HTML-тегами.

От нас требуется написать скрипт на языке JavaScript, который при наведении курсора мыши на HTML-элемент с атрибутом data-tooltip отобразит подсказку с текстом из этого атрибута. В большинстве случаев эта подсказка должна быть отображена над ее HTML-элементом.

Таким образом, автор HTML-страницы, вооружившись нашим скриптом, по задумке, сможет, добавляя в нужные ему HTML-элементы атрибут data-tooltip, придавать этим HTML-элементам способность отображать подсказку при наведении на них курсора мыши.

* * *

Похожую задачу «Поведение "подсказка"» я уже решал ранее к подразделу 2.3 «Делегирование событий» второй части обсуждаемого учебника. Я не писал пост с разбором той задачи, потому что не посчитал ее достаточно сложной, чтобы разбирать. Но ее решение — это база для решения задачи этого поста, поэтому придется сначала разобрать решение задачи «Поведение "подсказка"». Несмотря на то, что там для тестирования решения задана другая HTML-страница, само задание звучит почти так же, как и вышеизложенное задание в задаче этого поста. Единственное отличие состоит в том, что в задаче «Поведение "подсказка"» все HTML-элементы имеют родителем HTML-элемент body, а внутри них нет вложенных HTML-элементов, только простой текст. То есть все HTML-элементы находятся на одном уровне и являются соседями.

Чтобы охватить нашим скриптом все HTML-элементы в теле заданной HTML-страницы, используем делегирование событий: с помощью метода addEventListener повесим две функции (обработчики) на события mouseover (возникает при наведении курсора мыши на HTML-элемент) и mouseout (возникает при уходе курсора мыши с HTML-элемента) тела HTML-страницы. Тогда в этих функциях можно будет отловить указанные события на любом HTML-элементе, входящем в тело HTML-страницы. Пишем код:

document.addEventListener("mouseover", showTip);
document.addEventListener("mouseout", hideTip);

function showTip(event) {
    // ...тело функции...
}

function hideTip() {
    // ...тело функции...
}

Две функции (обработчики) я назвал showTip (показать подсказку) и hideTip (скрыть подсказку). В качестве первого параметра они получают объект event с информацией о произошедшем событии (слово «event» переводится на русский как «событие»).

По условиям задачи в каждый отдельный момент времени в браузере может быть показана только одна подсказка. Поэтому, думаю, логично будет создать глобальную переменную tip, которая будет содержать объект, представляющий подсказку. Тогда эта переменная будет доступна в обеих функциях-обработчиках, в которых нам нужно будет манипулировать подсказкой. Меняем код:

let tip = document.createElement("div"); // объект, представляющий подсказку
tip.className = "tooltip";               // ее CSS-класс
tip.hidden = true;                       // сначала подсказка скрыта
document.body.append(tip);               // добавим подсказку в тело HTML-страницы

document.addEventListener("mouseover", showTip);
document.addEventListener("mouseout", hideTip);

function showTip(event) {
    // ...тело функции...
}

function hideTip() {
    // ...тело функции...
}

CSS-класс tooltip уже описан на заданной HTML-странице. В этом CSS-классе для подсказки установлен тип позиционирования fixed (HTML-элемент фиксируется относительно левого верхнего угла области просмотра браузера). Нам остаётся вычислить и установить правильные координаты для подсказки, изменив CSS-свойства left и top для HTML-элемента, представляющего подсказку.

Включение видимости подсказки и установление координат подсказки будут производиться в функции showTip. В функции hideTip нам нужно лишь скрывать подсказку. Проще всего скрывать подсказку при событии mouseout на любом HTML-элементе тела заданной HTML-страницы. Даже если это совсем не тот HTML-элемент, к которому привязана подсказка, ничего страшного не произойдет: уже скрытая подсказка будет скрыта еще раз. Пользователь этого не заметит, а код функции hideTip будет максимально прост:
function hideTip() {
    tip.hidden = true;
}
С этой функцией закончили.

В функции showTip сначала определим, на какой HTML-элемент пользователь навел курсор. Если у этого HTML-элемента нет атрибута data-tooltip, завершим работу функции. В противном случае передадим в подсказку заданный в атрибуте data-tooltip текст и сделаем подсказку видимой:
function showTip(event) {
    let tar = event.target;
    if (!tar.dataset.tooltip) return;

    tip.innerHTML = tar.dataset.tooltip;
    tip.hidden = false;

    // ...вычисление координат подсказки и
    // перемещение HTML-элемента, представляющего подсказку...
}
Координаты подсказки будем вычислять после того, как сделаем ее видимой, потому что для невидимого HTML-элемента не получится определить его метрики (об этом было рассказано в подразделе 1.9 «Размеры и прокрутка элементов» второй части обсуждаемого учебника).

По условиям задачи в общем случае подсказка должна быть отображена над соответствующим HTML-элементом. С помощью метода getBoundingClientRect получим координаты целевого HTML-элемента относительно левого верхнего угла области просмотра браузера. Далее вычислим координаты подсказки так, чтобы она оказалась на 5px выше целевого HTML-элемента (таковы условия задачи) и переместим подсказку в нужное место. Меняем код функции showTip:
function showTip(event) {
    let tar = event.target;
    if (!tar.dataset.tooltip) return;

    tip.innerHTML = tar.dataset.tooltip;
    tip.hidden = false;

    let tarRect = tar.getBoundingClientRect(); // координаты HTML-элемента
    let x, y;                                  // координаты подсказки

    x = tarRect.x;                             // подсказка над
    y = tarRect.y - tip.offsetHeight - 5;      // HTML-элементом

    tip.style.left = x + "px";                 // перемещаем подсказку
    tip.style.top = y + "px";                  // в нужное место
}

На этом этапе задачу можно было бы считать выполненной, но при позиционировании подсказки в задаче требуется учесть два дополнительных момента: 1) подсказку следует размещать с горизонтальным выравниванием по центру целевого HTML-элемента (при этом подсказка не должна вылезти за левую границу области просмотра браузера); 2) если отображенная над целевым HTML-элементом подсказка вылезла за верхнюю границу области просмотра браузера, то ее следует отобразить под целевым HTML-элементом. Меняем код функции showTip:

function showTip(event) {
    let tar = event.target;
    if (!tar.dataset.tooltip) return;

    tip.innerHTML = tar.dataset.tooltip;
    tip.hidden = false;

    let tarRect = tar.getBoundingClientRect(); // координаты HTML-элемента
    let x, y;                                  // координаты подсказки
                                               // подсказка по центру HTML-элемента
    x = tarRect.x + tar.offsetWidth / 2 - tip.offsetWidth / 2;
    if (x < 0) x = 0;                          // корректируем, если вылезла слева
    
    y = tarRect.y - tip.offsetHeight - 5;      // подсказка над HTML-элементом
    if (y < 0) y = tarRect.y + tar.offsetHeight + 5; // или под ним

    tip.style.left = x + "px";                 // перемещаем подсказку
    tip.style.top = y + "px";                  // в нужное место
}

Отмечу, что вышеуказанная формула при горизонтальном выравнивании подсказки учитывает все три возможных случая: 1) ширина подсказки меньше ширины HTML-элемента, 2) ширина подсказки больше ширины HTML-элемента, 3) ширина подсказки равна ширине HTML-элемента.

Задача «Поведение "подсказка"» решена.

* * *

Вернемся к исходной задаче «Улучшенная подсказка». Заданная тестовая HTML-страница здесь посложнее: HTML-элементы с атрибутом data-tooltip присутствуют на разных уровнях DOM-дерева. Один из таких HTML-элементов может быть вложен в другой такой HTML-элемент, причем глубина вложенности не ограничена. Примерно вот так выглядит тело тестовой HTML-страницы:

<div data-tooltip="Здесь домашний интерьер" id="house">
  <div data-tooltip="Здесь крыша" id="roof"></div>

  <p>Жили-были на свете три поросенка.</p>

  <p>Даже имена у них были похожи.</p>

  <p>Но вот наступила осень. <a href="https://ru.wikipedia.org" data-tooltip="Читать далее...">Наведи курсор на меня</a></p>

</div>

В этом коде есть внешний HTML-элемент div с идентификатором house и атрибутом data-tooltip, а в него вложены как HTML-элементы с атрибутом data-tooltip, так и HTML-элементы без атрибута data-tooltip. Причем в один из вложенных HTML-элементов без атрибута data-tooltip (параграф p) вложен HTML-элемент с атрибутом data-tooltip (ссылка a), то есть это уже второй уровень вложенности, если считать от самого внешнего HTML-элемента div.

Я применил на этой странице скрипт-решение для разобранной выше задачи «Поведение "подсказка"». Конечно, он работает. Но в некоторых случаях взаимодействие с браузером происходит не так, как требуется. Я заметил два таких случая.

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

По условиям задачи «Улучшенная подсказка» если курсор мыши наводится на «стопку» HTML-элементов, вложенных друг в друга, то отобразиться должна «самая глубокая» подсказка.

То есть у нас в данном случае «стопка» из двух HTML-элементов, один из которых вложен в другой. Подсказка есть только у «верхнего» (внешнего) HTML-элемента, тут выбирать не из чего, отразиться должна именно она. Таким образом, даже при наведении мыши на внутренний HTML-элемент без подсказки при этом должна отобразиться подсказка внешнего HTML-элемента.

Во-вторых, при переходе с HTML-элемента a (второй уровень вложенности), у которого есть подсказка, на его родительский HTML-элемент p (первый уровень вложенности), у которого нет подсказки, подсказка HTML-элемента a исчезает (так и должно быть), а подсказка HTML-элемента div, в который вложен целевой HTML-элемент p, не появляется (так, по идее, не должно быть).

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

Для решения задачи «Улучшенная подсказка» сначала хочется писать код на каждый случай, внося изменения в обе обрабатывающие события функции showTip и hideTip. Но, на самом деле, достаточно внести небольшие изменения только в функцию showTip. Меняем код:

function showTip(event) {
    // let tar = event.target;
    // if (!tar.dataset.tooltip) return;
    let tar = event.target.closest("[data-tooltip]");
    if (!tar) return;

    tip.innerHTML = tar.dataset.tooltip;
    tip.hidden = false;

    let tarRect = tar.getBoundingClientRect(); // координаты HTML-элемента
    let x, y;                                  // координаты подсказки
                                               // подсказка по центру HTML-элемента
    x = tarRect.x + tar.offsetWidth / 2 - tip.offsetWidth / 2;
    if (x < 0) x = 0;                          // корректируем, если вылезла слева
    
    y = tarRect.y - tip.offsetHeight - 5;      // подсказка над HTML-элементом
    if (y < 0) y = tarRect.y + tar.offsetHeight + 5; // или под ним

    tip.style.left = x + "px";                 // перемещаем подсказку
    tip.style.top = y + "px";                  // в нужное место
}

Я закомментировал первые две строки и вместо них написал две новые (помечены красным). В этом новом коде мы с помощью метода closest ищем в «стопке» вложенных HTML-элементов снизу вверх HTML-элемент с подсказкой (то есть с атрибутом data-tooltip), начиная от того HTML-элемента, на который перешел курсор мыши. Если HTML-элемент с подсказкой в «стопке» найден, то мы покажем подсказку для него, а не для того, на который действительно перешел курсор. В противном случае, если HTML-элемент с подсказкой вообще не найден, наша функция завершит работу, не делая подсказку видимой.

Задача решена.

Малыш на драйве, 2017, кинофильм, США

Оригинальное название фильма: «Baby Driver». Жанр: боевик, криминал, ограбления, гонки на автомобилях, музыка.



Фильм показался невероятно скучным. Четыре раза пытался его досмотреть, но не смог. Актеры при этом, кстати, отличные: во второстепенных ролях — Кевин Спейси, Джейми Фокс, Джон Бернтал (который снялся в главной роли в сериале «Каратель» 2017-2019 гг.). Актриса Лили Джеймс (в роли официантки, девушки главного героя) — красивая. То есть актеры хорошие, фильм — дорогой, но ужасно скучный за исключением нескольких сцен погонь на автомобилях. Главный герой показался глуповат.

Главный герой — подросток, угонщик автомобилей. Он отрабатывает долг некоему Доку (Кевин Спейси), который организует ограбления. После выплаты долга герой собирается завязать с криминалом и наслаждаться свиданиями со своей девушкой, которая работает официанткой. Однако, Док, угрожая убить близких главного героя, заставляет его пойти на очередное ограбление. Не знаю, чем всё закончилось, так как засыпал на этом моменте четыре раза. Главный герой не вызывает никакого сочувствия, только раздражение.

Однако, многим фильм нравится, что выражается в высокой оценке на «Кинопоиске»: 7,2. Соотношение рецензий: из 198 всего — 137 положительных, 31 отрицательная и 30 нейтральных. У фильма три номинации на «Оскар»: за лучший звук, лучший монтаж, лучший монтаж звука.

Учебник по JavaScript: ч.2: Введение в события

Прочел второй раздел «Введение в события» второй части («Браузер: документ, события, интерфейсы») учебника по JavaScript.

https://learn.javascript.ru

Часть 2. Браузер: документ, события, интерфейсы (в т.ч. 32 подраздела)

Разделы:

2. Введение в события (5 подразделов)

2.1 Введение в браузерные события
2.2 Всплытие и погружение
2.3 Делегирование событий
2.4 Действия браузера по умолчанию
2.5 Генерация пользовательских событий

Работа с событиями в браузере — это, собственно, то, ради чего был создан язык JavaScript изначально (придание веб-сайтам интерактивности). Сегодня его применение, конечно, сильно расширилось, но всё-таки.

К разделу есть интересные задачи, некоторые из них я разбирал в своих постах:
1. CSS: абсолютное позиционирование и содержащий блок
2. JavaScript: гоняю мяч по полю, CSS-анимация
3. JavaScript: плющим мяч, язык SVG
4. JavaScript: раскрывающееся меню
5. JavaScript: дерганье меню, шрифты в браузере
6. JavaScript: кнопки удаления сообщений
7. JavaScript: карусель картинок, строим каркас 1
8. JavaScript: карусель картинок, строим каркас 2
9. JavaScript: карусель картинок, функции для кнопок

Для их решения пришлось углубиться в язык CSS и в некоторые тонкости работы браузера, в частности, со шрифтами.

После решения задач к подразделу 2.1 «Введение в браузерные события» решение остальных задач из других подразделов обсуждаемого раздела проходит, как по маслу. Эти задачи не слишком простые, но и не сложные, среднего уровня, то, что нужно для учебника. И они при этом остаются достаточно интересными.

Наиболее интересным для меня показался подраздел 2.3 «Делегирование событий», сразу стало понятно, куда его можно применить в моих текущих проектах. Очень полезная штука. Позволяет для множества однотипных HTML-элементов, имеющих одного и того же родителя, писать только один обработчик события. Например, для обработки события щелчка мышью на одной из множества ячеек HTML-таблицы.

Подраздел 2.2 «Всплытие и погружение» необходим для понимания функционирования событий в браузере. В нем рассказано, что каждое событие на HTML-странице проходит фазу погружения, а затем фазу всплытия. Это означает, что каждое событие можно отловить на цепочке вложенных друг в друга HTML-элементов на пути вглубь вложенности («погружение»), а затем на обратном пути к самому внешнему HTML-элементу из вложенных друг в друга («всплытие»).

JavaScript: карусель картинок, функции для кнопок

Начало:
1. JavaScript: карусель картинок, строим каркас 1
2. JavaScript: карусель картинок, строим каркас 2

Заканчиваю разбор задачи «Карусель» к подразделу 2.1 «Введение в браузерные события» второй части учебника по JavaScript.

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

Теперь нам осталось написать функционал для имеющихся на каркасе карусели кнопок «влево» и «вправо», которые заставят карусель из картинок двигаться.

Двигать мы будем HTML-список ul, в пунктах которого содержатся десять тестовых картинок. Чтобы дать HTML-списку способность передвигаться, изменим в его стиле тип позиционирования со статического (по умолчанию) на относительное с помощью указания position: relative;:
ul {
  margin: 0;             /* уберем внешние и внутренние умолчательные */
  padding: 0;            /* отступы, назначенные браузером */
  font-size: 0px;        /* уберем промежутки между картинками */
                         /* и промежутки для «свисающих» букв */
  width: 9999px;         /* разместим пункты списка в одну строку */
  position: relative;    /* даем списку способность двигаться */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
                         /* и уберем маркеры списка */
}

Теперь мы можем двигать HTML-список влево и вправо, изменяя из своего скрипта его CSS-свойство left (координата левого верхнего угла HTML-элемента ul).

Подробнее про CSS-позиционирование можно почитать тут (эту ссылку я уже давал неоднократно):
https://developer.mozilla.org/ru/docs/Learn/CSS/CSS_layout/Positioning

Сначала я написал две функции, по одной на каждую кнопку карусели («влево», «вправо»). А потом увидел, что эти функции похожи друг на друга за исключением двух моментов:

1) При нажатии на кнопку «влево» HTML-список движется вправо, то есть значение его CSS-свойства left увеличивается. А при нажатии на кнопку «вправо» HTML-список движется влево, то есть значение его CSS-свойства left уменьшается.

(Тут может появиться вопрос: почему при нажатии кнопок, указывающих в одном направлении, HTML-список движется в противоположном направлении? Ответ: тут мы действуем по тому же принципу, по которому в браузере работает вертикальная полоса прокрутки: если мы перемещаем полосу прокрутки вверх, то отображаемая HTML-страница движется вниз; если же мы перемещаем полосу прокрутки вниз, то отображаемая HTML-страница движется вверх. По тому же принципу работает и горизонтальная полоса прокрутки: направление движения полосы прокрутки противоположно движению отображаемой HTML-страницы.)

2) При движении нашего HTML-списка слева и справа существуют ограничители, далее которых наш HTML-список двигаться не должен, иначе картинки в карусели «уедут» за левую или правую границу области просмотра карусели. Ограничитель при движении влево отличается от ограничителя при движении вправо. В качестве ограничителя будем использовать определенное значение CSS-свойства left HTML-списка.

Так как две функции отличались только в этих двух моментах, я решил объединить эти две функции в одну, чтобы не дублировать код. Дополняем код скрипта из предыдущих двух постов следующим кодом:
// количество картинок в шаге карусели
const STEP = 3;
// количество картинок в карусели всего
const NUM_PIC = document.querySelectorAll("ul img").length;

arrowLeft.onclick = () => move(1);  // кнопка «влево»
arrowRight.onclick = () => move(0); // кнопка «вправо»

function move(direction) {
    let ul = document.querySelector("ul");
    let x = ul.style.left;        // получим координату списка
    if (x == "") { x = 0; }
    else { x = x.slice(0, -2); }  // удалим символы "px"
    
    if (direction) {
        x = +x + PIC_SIZE * STEP; // двигаем список вправо (кнопка «влево»)
        if (x > 0) x = 0;         // ограничитель справа
    } else {
        x = +x - PIC_SIZE * STEP; // двигаем список влево (кнопка «вправо»)
        let min = -(NUM_PIC * PIC_SIZE - VIEWPORT_SIZE);
        if (x < min) x = min;     // ограничитель слева
    }
    
    ul.style.left = x + "px";     // запишем вычисленную координату
}

Константы PIC_SIZE и VIEWPORT_SIZE были определены в предыдущих двух постах. PIC_SIZE — это ширина (она же — высота) каждой из картинок в карусели, а VIEWPORT_SIZE — ширина области просмотра карусели (мы ее определили такой, чтобы в область просмотра смогли влезть три картинки, то есть 3 * PIC_SIZE).

С помощью константы STEP, кстати, можно поменять количество картинок, прокручиваемых в карусели за одно нажатие на кнопку «влево» или «вправо» (при работе скрипта фактическое значение шага прокрутки карусели может быть и меньше, если при очередном шаге карусель достигла левого или правого ограничителя). Я сделал эту константу равной числу 3, потому что так работает действующий пример карусели от авторов задачи. Но мне больше нравится, когда эта константа равна числу 1, тогда картинки в карусели «листаются» по одной.

Если по ограничителю справа всё понятно, это число 0, то по ограничителю слева могут понадобиться пояснения. Если ограничитель слева будет равен ширине всех тестовых картинок NUM_PIC * PIC_SIZE, то все картинки «уедут» за левую границу области просмотра карусели. А хочется, чтобы в области просмотра карусели при этом оставались видны последние в списке картинки. Чтобы это реализовать, корректируем ограничитель слева, вычитая из ширины всех тестовых картинок ширину области просмотра карусели.

Задача решена.

CSS-анимация

Я уже разбирал анимацию в одном из предыдущих постов (тут). Чтобы анимировать движение HTML-списка в нашей карусели, сделаем то же, что и раньше: воспользуемся CSS-свойством transition (для анимации самого первого движения HTML-списка добавим в стиль HTML-списка еще и первоначальное значение CSS-свойства left). Меняем код стиля HTML-списка (изменения я выделил красным):
ul {
  margin: 0;             /* уберем внешние и внутренние умолчательные */
  padding: 0;            /* отступы, назначенные браузером */
  font-size: 0px;        /* уберем промежутки между картинками */
                         /* и промежутки для «свисающих» букв */
  width: 9999px;         /* разместим пункты списка в одну строку */
  position: relative;    /* даем списку способность двигаться */
  transition: all 1s;    /* анимация движения картинок */
  left: 0;               /* для анимации самого первого движения картинок */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
                         /* и уберем маркеры списка */
}

Дополнительно

В коде заданной HTML-страницы авторы задачи дают ученику небольшой скрипт на языке JavaScript, не имеющий отношения к финальному решению, которое напишет ученик. Этот скрипт при желании можно использовать при написании решения задачи, он может играть вспомогательную роль при тестировании решения. Вот этот скрипт:
// отметить картинки для удобства разработки
// этот код может быть удалён по вашему усмотрению
let i = 1;
for(let li of document.querySelectorAll("li")) {
    li.style.position = "relative";
    li.insertAdjacentHTML("beforeend", `<span style="position:absolute; left:0; top:0;">${i}</span>`);
    i++;
}

Этот скрипт должен показывать в левом верхнем углу каждой картинки в карусели порядковый номер этой картинки. Такая нумерация, по идее, должна помочь не запутаться в картинках при прокрутке карусели влево-вправо. На этапе разработки вычисления координат HTML-списка при его движении нумерация картинок действительно может быть полезна.

Однако, авторы задачи забыли, что при разработке каркаса карусели (см. два предыдущих поста) в стиле HTML-списка мы внесли указание font-size: 0px;, делающее любой текст в списке невидимым. Поэтому вышеуказанный скрипт в показанной редакции не будет работать так, как было задумано.

Я добавил в определение стиля HTML-элемента span в обсуждаемом скрипте указание font-size: 16px; и скрипт заработал. Работающая редакция скрипта (красным я выделил изменение):
// отметить картинки для удобства разработки
// этот код может быть удалён по вашему усмотрению
let i = 1;
for(let li of document.querySelectorAll("li")) {
    li.style.position = "relative";
    li.insertAdjacentHTML("beforeend", `<span style="position:absolute; font-size: 16px; left:0; top:0;">${i}</span>`);
    i++;
}

Еще правильнее, наверное, будет указание font-size: initial;, то есть размер шрифта, указанный в настройках браузера по умолчанию.

JavaScript: карусель картинок, строим каркас 2

Начало: JavaScript: карусель картинок, строим каркас 1.

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

У нас уже есть HTML-список из 10 тестовых картинок, настройку стилей которых я рассматривал в предыдущем посте.

Для требуемой карусели картинок, как мне показалось, нужно создать что-то вроде отдельной, своей собственной, области просмотра (по аналогии с областью просмотра браузера), в которой можно будет показывать заданные картинки.

Как создавать эту отдельную область просмотра, я уже примерно представлял, опираясь на стили футбольного поля из задач, разбиравшихся в предыдущих постах (например, тут). Саму эту отдельную область просмотра можно реализовать HTML-элементом div, в котором нашей областью просмотра станет клиентская часть HTML-элемента div (то есть его содержимое и внутренние отступы padding). Чтобы спрятать содержимое, выходящее за пределы нашей области просмотра (клиентской части HTML-элемента div), при создании футбольного поля использовалось указание overflow: hidden; в стиле футбольного поля. То же сделаем и мы. Меняем код:

Тело заданной HTML-страницы:
<div id="viewport">
  <ul>
    <li><img src="https://ru.js.cx/carousel/1.png"></li>
    <li><img src="https://ru.js.cx/carousel/2.png"></li>
    <li><img src="https://ru.js.cx/carousel/3.png"></li>
    <li><img src="https://ru.js.cx/carousel/4.png"></li>
    <li><img src="https://ru.js.cx/carousel/5.png"></li>
    <li><img src="https://ru.js.cx/carousel/6.png"></li>
    <li><img src="https://ru.js.cx/carousel/7.png"></li>
    <li><img src="https://ru.js.cx/carousel/8.png"></li>
    <li><img src="https://ru.js.cx/carousel/9.png"></li>
    <li><img src="https://ru.js.cx/carousel/10.png"></li>
  </ul>
</div>

К стилям добавляем стиль области просмотра карусели:
div#viewport {      /* стиль области просмотра карусели */
  overflow: hidden; /* скрыть картинки, выходящие за область просмотра */
}

Я решил писать скрипт, который будет отталкиваться от размера картинок, указанного в стиле картинок, хоть это и не требуется по условиям задачи. Мне хотелось, чтобы размер картинок в карусели можно было бы поменять в одном месте, в стиле картинок. Пишем код скрипта:
// размер картинок (все картинки — равные друг другу квадраты)
const PIC_SIZE = document.querySelector("ul img").offsetWidth;
// размер области просмотра карусели картинок
const VIEWPORT_SIZE = 3 * PIC_SIZE;

// предварительная настройка каркаса карусели
viewport.style.width = VIEWPORT_SIZE + "px";

Как видно из этого кода, область просмотра карусели вмещает три картинки из заданных десяти (остальные пока скрыты, так как выходят за область просмотра карусели).

Сначала я думал, что для построения карусели мне хватит одного HTML-элемента div. Но потом я стал прикидывать, куда здесь вставить кнопки прокрутки карусели (их две: влево и вправо), и понял, что, по идее, нужен еще один HTML-элемент div (собственно, сама карусель), который будет содержать две кнопки и область просмотра карусели. Меняем код:

Меняем тело заданной HTML-страницы:
<div id="carusel">
  <div id="viewport">
    <ul>
      <li><img src="https://ru.js.cx/carousel/1.png"></li>
      <li><img src="https://ru.js.cx/carousel/2.png"></li>
      <li><img src="https://ru.js.cx/carousel/3.png"></li>
      <li><img src="https://ru.js.cx/carousel/4.png"></li>
      <li><img src="https://ru.js.cx/carousel/5.png"></li>
      <li><img src="https://ru.js.cx/carousel/6.png"></li>
      <li><img src="https://ru.js.cx/carousel/7.png"></li>
      <li><img src="https://ru.js.cx/carousel/8.png"></li>
      <li><img src="https://ru.js.cx/carousel/9.png"></li>
      <li><img src="https://ru.js.cx/carousel/10.png"></li>
    </ul>
  </div>
</div>

Меняем стиль области просмотра карусели, добавим внешние отступы. Сверху и снизу — для красоты, слева и справа — чтобы выделить место под кнопки:
div#carusel {         /* стиль карусели */
  border: 1px solid gray;    /* от границы отсчитываются отступы области просмотра */
}

div#viewport {        /* стиль области просмотра карусели */
  margin: 10px 50px;         /* 50px — слева и справа, место для кнопок */
  overflow: hidden;          /* скрыть картинки, выходящие за область просмотра */
  background-color: #f0f0f0; /* фон области просмотра */
}

Добавляем настройку ширины карусели в скрипт:
// размер картинок (все картинки — равные друг другу квадраты)
const PIC_SIZE = document.querySelector("ul img").offsetWidth;
// размер области просмотра карусели картинок
const VIEWPORT_SIZE = 3 * PIC_SIZE;

// предварительная настройка каркаса карусели
viewport.style.width = VIEWPORT_SIZE + "px";
carusel.style.width = 50 + VIEWPORT_SIZE + 50 + "px";

Вот что получилось в моём браузере «Microsoft Edge» на движке «Chromium» (картинка):



Тонкая серая линия слева и сверху — это граница области просмотра браузера. На этой иллюстрации видна область просмотра карусели (у нее серый фон), в которой видны первые три картинки из десяти заданных (остальные семь скрыты, потому что выходят за правую границу области просмотра карусели). Также видна темно-серая граница самой карусели. Между границей карусели и границей области просмотра слева и справа виден промежуток в 50px, предназначенный для кнопок карусели.

Переходим к созданию кнопок. Вставим их код (он уже был дан изначально в коде заданной HTML-страницы) в тело HTML-страницы в содержимое карусели перед областью просмотра карусели:
<div id="carusel">
  <button class="arrow">&#8678;</button> <!-- кнопка «влево» -->
  <button class="arrow">&#8680;</button> <!-- кнопка «вправо» -->
  <div id="viewport">
    <ul>
      <li><img src="https://ru.js.cx/carousel/1.png"></li>
      <li><img src="https://ru.js.cx/carousel/2.png"></li>
      <li><img src="https://ru.js.cx/carousel/3.png"></li>
      <li><img src="https://ru.js.cx/carousel/4.png"></li>
      <li><img src="https://ru.js.cx/carousel/5.png"></li>
      <li><img src="https://ru.js.cx/carousel/6.png"></li>
      <li><img src="https://ru.js.cx/carousel/7.png"></li>
      <li><img src="https://ru.js.cx/carousel/8.png"></li>
      <li><img src="https://ru.js.cx/carousel/9.png"></li>
      <li><img src="https://ru.js.cx/carousel/10.png"></li>
    </ul>
  </div>
</div>
Символы стрелок я заменил их кодами в Юникоде. Это было необязательно, но мне так больше нравится: сразу понятно, о каком именно символе идет речь.

К тому же, в моем текстовом редакторе «Notepad++» по умолчанию используется шрифт «Courier New», в котором нет таких символов-стрелок, поэтому вместо них отражаются квадратики (замещающие символы на случай, когда определенных символов в шрифте нет). При этом в браузере эти символы-стрелки успешно отображаются (я ранее писал, почему: браузер может подобрать и отобразить требуемые символы из других шрифтов, если в текущем шрифте их нет).

Вставленные в карусель кнопки нужно правильно разместить (позиционировать). В стилях включим для них абсолютное позиционирование (указание position: absolute; в стиле кнопок) относительно родительского HTML-элемента (указание position: relative; в стиле карусели):
.arrow {              /* стиль кнопок */
  position: absolute;        /* абсолютное позиционирование относительно карусели */
}

div#carusel {         /* стиль карусели */
  border: 1px solid gray;    /* от границы отсчитываются отступы области просмотра */
  position: relative;        /* для позиционирования кнопок */
}

div#viewport {        /* стиль области просмотра карусели */
  margin: 10px 50px;         /* 50px — слева и справа, место для кнопок */
  overflow: hidden;          /* скрыть картинки, выходящие за область просмотра */
  background-color: #f0f0f0; /* фон области просмотра */
}

Сами координаты вычислим в скрипте. Дополняем код:
// размер картинок (все картинки — равные друг другу квадраты)
const PIC_SIZE = document.querySelector("ul img").offsetWidth;
// размер области просмотра карусели картинок
const VIEWPORT_SIZE = 3 * PIC_SIZE;

// настройка ширины карусели и области просмотра карусели
viewport.style.width = VIEWPORT_SIZE + "px";
carusel.style.width = 50 + VIEWPORT_SIZE + 50 + "px";

// позиционируем кнопки
let arrowLeft  = document.querySelectorAll(".arrow")[0],
    arrowRight = document.querySelectorAll(".arrow")[1];
arrowLeft.style.left = "10px";
arrowLeft.style.top = (10 + PIC_SIZE + 10) / 2 - arrowLeft.offsetHeight / 2 + "px";
arrowRight.style.right = "10px";
arrowRight.style.top = (10 + PIC_SIZE + 10) / 2 - arrowRight.offsetHeight / 2 + "px";
По горизонтали кнопки располагаются на расстоянии 10px от границы карусели. По вертикали координата вычисляется так, чтобы кнопка оказалась на половине высоты карусели. При этом высоту, как мы помним из предыдущих задач, нужно скорректировать на высоту самой кнопки, потому что мы указываем координаты левого (и правого для другой кнопки) верхнего угла кнопки, а не ее центра.

Вот что получилось в моем браузере (картинка):



На этом построение каркаса карусели закончим и перейдем к написанию ее функционала. Я в своем решении в итоге настроил стили так, что карусель получилась в точности такая же, как и в действующем примере от авторов задачи, который можно рассмотреть на странице задачи. Но, в принципе, дальше уже идет настройка цветов HTML-элементов и их частей, закругления углов HTML-элементов (с помощью указания, к примеру, border-radius: 15px;) и тому подобное, что не слишком важно для изучения текущего подраздела обсуждаемого учебника.

Продолжение тут.

JavaScript: карусель картинок, строим каркас 1

Решил задачу «Карусель» к подразделу 2.1 «Введение в браузерные события» второй части учебника по JavaScript.

Задача состоит в том, чтобы на HTML-странице создать карусель картинок. Что такое «карусель картинок»? Это механизм, с помощью которого пользователь может листать имеющиеся картинки кнопками влево и вправо. В таком виде, бывает, оформляют фотоальбом на сайте или фотоальбомы в соцсетях.

В задаче дана HTML-страница, на которой частично уже построена карусель картинок. Требуется достроить каркас карусели, созданный на языках HTML и CSS, а затем придать карусели интерактивность, написав функционал кнопок на языке JavaScript. Код заданной HTML-страницы можно посмотреть в песочнице.

Мне было интересно сначала понять, как создали каркас карусели на языках HTML и CSS.

Для постройки тестовой карусели в задаче дано десять картинок:
https://ru.js.cx/carousel/1.png
https://ru.js.cx/carousel/2.png
...
https://ru.js.cx/carousel/10.png

На этих картинках изображено десять разных смайликов.

Задача изначально упрощена: как я понимаю, предполагается, что все картинки представляют собой одинаковые квадраты (размеры разных картинок равны друг другу, ширина и высота каждой картинки тоже равны друг другу). Для хранения этого размера в будущем скрипте будем использовать константу PIC_SIZE, равную 130px (это размер оригиналов заданных картинок).

В теле HTML-страницы картинки помещены в пункты HTML-списка:
<ul>
  <li><img src="https://ru.js.cx/carousel/1.png"></li>
  <li><img src="https://ru.js.cx/carousel/2.png"></li>
  <!-- ... -->
  <li><img src="https://ru.js.cx/carousel/10.png"></li>
</ul>

У меня в браузере («Microsoft Edge» на движке «Chromium») это выглядит так (картинка):



Серая линия слева и сверху — это граница области просмотра браузера. Толстые черные точки слева от картинок смайликов — это значки пунктов списка.

Для нашей карусели картинок требуется, чтобы картинки располагались не вертикально, а горизонтально, очередью. Так как у нас картинки размещены в пунктах HTML-списка, нам требуется расположить пункты списка горизонтально, друг за другом. Сделаем это, настраивая стили HTML-списка и его пунктов на языке CSS. Стили в задаче размещены в отдельном файле style.css, включенном на заданную HTML-страницу.

По умолчанию тип отображения (CSS-свойство display) пунктов HTML-списка равен значению list-item. В нашем случае он ведет себя как блочный элемент, то есть занимает всю ширину области просмотра. Кроме этого, этот тип отображения генерирует перед содержимым пункта псевдо-элемент ::marker (значок пункта списка). Поменяв тип отображения на значение inline-block, мы добьемся исчезновения псевдо-элементов ::marker у пунктов списка, а также того, что пункты списка смогут располагаться горизонтально, друг за другом:
ul li {
  display: inline-block; /* разместить пункты списка горизонтально */
}

У меня в браузере это выглядит так (картинка):



У HTML-списка браузеры по умолчанию обычно устанавливают внутренние (padding) и внешние (margin) отступы. В нашей карусели картинок эти отступы не нужны (могут усложнить расчёты в скрипте на языке JavaScript), поэтому уберем их:
ul {
  margin: 0;             /* уберем внешние и внутренние */
  padding: 0;            /* умолчательные отступы от браузера */
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
}

Для картинок (это уже объяснялось в нескольких предыдущих постах) в стиле картинок укажем их ширину и высоту, чтобы браузер в любой ситуации знал размер содержимого пунктов списка и мог быстро отобразить картинки и пункты списка, не роясь в кэше в поисках размеров картинок. (Также таким образом исключаем ситуацию, когда при первой загрузке HTML-страницы браузер не знает размеров картинок и расчеты в скрипте на языке JavaScript, отталкивающиеся от размеров картинок, могут в этом случае дать неверные результаты.) Меняем код стилей:
ul {
  margin: 0;             /* уберем внешние и внутренние */
  padding: 0;            /* умолчательные отступы от браузера */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
}

Также в нашей карусели картинок нам не нужны и промежутки между картинками (тоже могут усложнить расчёты в скрипте на языке JavaScript). Есть ли они? Чтобы проверить это, временно сделаем красную рамку вокруг каждого пункта HTML-списка и серый фон для каждой картинки (оригиналы картинок имеют прозрачный фон):
ul {
  margin: 0;             /* уберем внешние и внутренние */
  padding: 0;            /* умолчательные отступы от браузера */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
  background-color: silver; /* временно, серый фон */
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
  border: 1px solid red;    /* временно, красная рамка */
}

У меня в браузере получилось следующее (картинка):


Во-первых, обратим внимание на то, что между нашим HTML-списком и границей области просмотра браузера всё еще есть промежуток (у меня в браузере он равен 8px). Это внешний отступ (margin), устанавливаемый браузером по умолчанию для тела (body) HTML-страницы. Нам он не помешает, потому что в будущем мы вставим наш HTML-список внутрь другого контейнера, HTML-элемента div.

Во-вторых, на иллюстрации видно, что между отдельными пунктами нашего HTML-списка (напомню, пункты временно обведены красной границей-рамкой для наглядности) есть промежуток. Откуда он взялся? На самом деле этот промежуток есть в исходном коде нашей HTML-страницы, это пробельные символы между HTML-элементами li (напомню, в состав пробельных символов входят не только пробелы, но и символы новой строки, и символы табуляции).

Избавиться от этого промежутка можно несколькими способами. Например, можно удалить пробельные символы между HTML-элементами li в исходном коде нашей HTML-страницы. Но тогда исходный код нашей HTML-страницы станет плохо читаемым (все пункты HTML-списка будут слеплены в одну строку). Как я видел, из исходного кода HTML-страниц многих сайтов, сделанных профессионалами, убраны все лишние пробельные символы. Там это оправдано, потому что пользователь исходный код HTML-страницы не читает (ему достаточно отображения HTML-страницы в браузере), а кодеры пользуются специальными программами, которые убирают лишние пробельные символы только в самом конце написания HTML-страницы, перед ее загрузкой на сайт.

В нашем же случае я не хотел бы убирать пробельные символы в коде HTML-списка, чтобы не ухудшать читаемость кода заданной HTML-страницы. Тогда можно применить другой способ: сделать размер шрифта равным нулю с помощью указания font-size: 0; в стиле нашего HTML-списка.

В-третьих, на иллюстрации видно, что внутри каждого пункта нашего HTML-списка, между нижней границей картинки со смайликом и нижней границей самого пункта тоже присутствует некий промежуток (у меня в браузере он составил 4px). Откуда он взялся?

Дело в том, что HTML-элемент img по умолчанию является строчным HTML-элементом, то есть его тип отображения — inline. А браузер старается расположить все строчные HTML-элементы в строке так, чтобы все они находились на базовом уровне строки. Что это за уровень? Это линия, на которой «стоят» все буквы в строке. У некоторых букв элементы этих букв «свисают» ниже базовой линии строки. В разных шрифтах по-разному, но зачастую, к примеру, у русской строчной буквы «у» или у английской буквы «g» нижняя закорючка «свисает» ниже базовой линии. И таких букв не так уж и мало в алфавитах и шрифтах. На этот случай браузер резервирует для строчных HTML-элементов определенное пространство ниже базового уровня (базовой линии) строки (в данном случае — 4px).

Чтобы проиллюстрировать описанное, добавим к нашему HTML-списку временный пункт с текстом:
<ul>
  <li><img src="https://ru.js.cx/carousel/1.png"></li>
  <li><img src="https://ru.js.cx/carousel/2.png"></li>
  <!-- ... -->
  <li><img src="https://ru.js.cx/carousel/10.png"></li>
  <li>Буквы: ф, р, g.</li> <!-- временный пункт -->
</ul>

Вот как это выглядит у меня в браузере (картинка):



Увеличим (я увеличил кусочек предыдущей картинки в графическом редакторе в 4 раза):



На этой иллюстрации я обозначил базовую линию тонкой красной линией. Текст четвертого пункта нашего HTML-списка отображен шрифтом по умолчанию (в моем браузере в настройках шрифтом по умолчанию установлен «Times New Roman»), размер шрифта тоже — по умолчанию (в моем браузере это 16px). В этом шрифте при этом размере строчные русские буквы «у», «ф», «р» и строчная английская буква «g», как видно на иллюстрации, «свисают» ниже базовой линии строки на 3 пикселя. Запятая «свисает» ниже базовой линии строки на 2 пикселя.

Теперь стало понятно, откуда взялся рассматриваемый промежуток. А как его убрать? Тут тоже есть несколько способов. Например, если поменять тип отображения HTML-элемента img с умолчательного inline на block с помощью указания display: block; в стиле HTML-элемента img, то HTML-элемент img станет не строчным, а блочным, браузер не станет резервировать место под «свисающие» буквы и рассматриваемый промежуток исчезнет.

Другой вариант — это вышеупомянутое указание font-size: 0; в стиле нашего HTML-списка. Если размер шрифта будет равен нулю, то браузеру не будет нужды резервировать место под «свисающие буквы» и рассматриваемый промежуток опять же исчезнет. Поэтому, думаю, стоит остановиться на этом варианте, потому что он устраняет сразу две проблемы (два вида промежутков).

Итак, мы организовали наш HTML-список картинок в горизонтальную очередь и убрали все ненужные отступы и промежутки между картинками. Однако, все вышеприведенные тесты проводились только для трех картинок. Если теперь добавить в HTML-список все 10 заданных изначально картинок, то те картинки, которые не поместятся в строке по ширине области просмотра браузера, перейдут на следующую строку. А нам нужно, чтобы все заданные картинки представляли очередь только в одну строку, даже если она выйдет за пределы области просмотра браузера. Для этого в стиль HTML-списка можно добавить указание width: 9999px;. Не обязательно, конечно, было брать такую большую ширину, но а вдруг в будущем нам захочется добавить картинок в карусель? Запас не помешает.

В итоге мы пока получили вот такой файл со стилями:
ul {
  margin: 0;             /* уберем внешние и внутренние умолчательные */
  padding: 0;            /* отступы, назначенные браузером */
  font-size: 0px;        /* уберем промежутки между картинками */
                         /* и промежутки для «свисающих» букв */
  width: 9999px;         /* разместим пункты списка в одну строку */
}

ul img {
  width: 130px;          /* размеры картинок */
  height: 130px;
}

ul li {
  display: inline-block; /* разместим пункты списка горизонтально */
                         /* и уберем маркеры списка */
}

Продолжение тут.