JS30. Задание 16 CSS Text Shadow Mouse Move Effect

Демо https://js3014.github.io/
Код  https://github.com/js3014/js3014.github.io

Создаём эффект движения тени текста вслед за мышкой. В видео автор демонстрирует этот эффект на примере заголовка одного из своих курсов по флексбоксах: https://flexbox.io/ но на данный момент эффект движения он убрал, оставив только статичные тени.

html-разметка очень простая: один блок и заголовок в нём:

<div class="hero">
            <h1 contenteditable>🔥WOAH!</h1>
</div>

Смайлик с костром можно скопировать здесь: https://emojipedia.org/fire/
(другие смайлики там тоже есть)

UPD: почему-то у меня смайлик исчез. как же его автор добавлял?

Стили выглядят так:

.hero {
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    color: black;
}

h1 {
    text-shadow: 10px 10px 0 rgba(0,0,0,1);
    font-size: 100px;
}

В очередной раз порадовалась простоте центрирования элемента при помощи флексбоксов, чтобы разместить блок по центру страницы достаточно указать ему свойства:

display: flex;
justify-content: center;
align-items: center;

и какую-нибудь высоту, если высота не существенна, указываем min-height

Переходим к js.

1. Находим элементы: блок hero и заголовок h1 внутри этого блока:

var hero = document.querySelector(".hero");
var text = hero.querySelector("h1");

Создаём функцию

function shadow(event) {
    //TODO: Magic
}

Блоку hero добавляем слушатель, который будет отслеживать движения мышки и запускать функцию shadow если мышка движется

hero.addEventListener("mousemove", shadow);

Выведем событие event в консоль


В свойствах объекта MouseEvent видим offsetX и  offsetY, показывающие отступ курсора мыши по горизонтали и вертикали от края целевого DOM узла.

Нам понадобятся размеры: ширина и высота нашего блока:

var { offsetWidth: width, offsetHeight: height } = hero;

А также текущее положение мыши:

var { offsetX: x, offsetY: y } = event;

С таким присваиванием сталкиваюсь впервые и не очень его понимаю 
Автор говорит, что для сокращения количества кода использует возможности, предоставленные ES6

Код можно сделать более очевидным и понятным, хоть и менее лаконичным, если вместо

var { offsetWidth: width, offsetHeight: height } = hero;

написать

var width = hero.offsetWidth;
var height = hero.offsetHeight;
 
а строки

var { offsetX: x, offsetY: y } = event;

заменить на

x = event.offsetX;
y = event.offsetY;


Выведем в консоль значения console.log(x, y);

console.log(x, y);

Верхний левый угол страницы имеет координаты 0, 0, нижний правый 800, 800.
Но когда курсор попадает на блок с заголовком, в нём координаты курсора показывают положение заголовка относительно блока, и 0, 0 это уже верхний левый угол блока.

Если я правильно понимаю, это связано с всплытием (или погружением?): так как у целевого элемента hero есть потомок h1, то слушатель hero.addEventListener отслеживает события и на блоке и на заголовке тоже.
Вообще, это удобно и правильно, но в данном случае такое поведение нас не устраивает.

Автор предлагает добавить условие:

if (this !== event.target) {
        x = x + event.target.offsetLeft;
        y = y + event.target.offsetTop;
    }


Здесь event.target - элемент, который вызывает событие (блок hero). Если целевой элемент не блок (а заголовок) то х и у равно смещение курсора внутри блока плюс расстояние блока от левого или верхнего края страницы.

Попробовала предотвратить распространение событий без условия при помощи event.stopImmediatePropagation(); или event.stopPropagation(); - не работает. Но ведь должно?

Размер смещения тени текста выбираем по своему усмотрению

var walk = 500; // 500px

Дальше пошла математика и воображение. Нам нужно, чтобы при движении курсора тени смещались симметрично, относительно текста.
Для этого автор использует формулы:

var xWalk = Math.round((x / width * walk) - (walk / 2));
var yWalk = Math.round((y / height * walk) - (walk / 2));


Формулы, пожалуй, слегка не очевидны, но они работают.

Остался последний шаг: добавить тени для текста и стили для них.
Добавим вначале одну тень:

text.style.textShadow = 10px 10px 0 red;

Первое 10рх - смещение тени по горизонтали, второе 10рх - смещение тени по вертикали, 0 - размытие, red - цвет.

Теперь изменим смещение на наши переменные  xWalk и yWalk. Получим:

text.style.textShadow = `${xWalk}px ${yWalk}px 0 red`;

Прекрасно. Работает!

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

text.style.textShadow = `
${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7),
${xWalk * -1}px ${yWalk}px 0 rgba(0,255,255,0.7),
${yWalk}px ${xWalk * -1}px 0 rgba(0,255,0,0.7),
${yWalk * -1}px ${xWalk}px 0 rgba(0,0,255,0.7)
`;

Тоже работает.

Код полностью:

var hero = document.querySelector(".hero");
var text = hero.querySelector("h1");
var walk = 500; // 500px

function shadow(event) {
    var width = hero.offsetWidth;
    var height = hero.offsetHeight;
   
    x = event.offsetX;
    y = event.offsetY;
      
    if (this !== event.target) {
        x = x + event.target.offsetLeft;
        y = y + event.target.offsetTop;
    }

    var xWalk = Math.round((x / width * walk) - (walk / 2));
    var yWalk = Math.round((y / height * walk) - (walk / 2));

    text.style.textShadow = `
${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7),
${xWalk * -1}px ${yWalk}px 0 rgba(0,255,255,0.7),
${yWalk}px ${xWalk * -1}px 0 rgba(0,255,0,0.7),
${yWalk * -1}px ${xWalk}px 0 rgba(0,0,255,0.7)
`;

}

hero.addEventListener("mousemove", shadow);