Android Linux Windows

Глаз следящий за курсором javascript. Скрипт движение за курсором

Но на базе древнего кода, так что работать толком не будет ни в новых IE 10-11, ни в новой линейке Firefox, а может, и в других браузерах, других под рукой нет.

Старый заслуженный код корёжить не хочется, сделаем движение картинки за курсором мыши по-современному и простому. Для скорости разработки реализуем только движение изображения за курсором, ведь при необходимости можно в функции getClickPosition определить более сложные правила для установки требуемых координат картинки (picture.style.left и picture.style.top), например, как-то зависящие от расстояния между рисунком и курсором мыши, как в том коде с круговым движением.

Задачу решим за 3 простых шага.

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

Много контента, чтоб у BODY была высота, где разбегаться
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

2. Поставим в теге яваскрипта обработчик события, связанного с движением курсора мыши:

document.body.addEventListener("mousemove", getClickPosition, false);

3. В этом же теге следом напишем функции, которые умеют вычислять настоящее положение курсора мыши, может, мои и не самые лучшие:

Function getClickPosition (e) { //слушатель события движения мыши var picture = document.querySelector("#thing"); var parentPosition = getPosition(e.currentTarget); var xPosition = e.clientX - parentPosition.x - (picture.clientWidth / 2); var yPosition = e.clientY - parentPosition.y - (picture.clientHeight / 2); picture.style.left = xPosition + "px"; picture.style.top = yPosition + "px"; } function getPosition(element) { //расчёт позиции элемента var xPos = 0; var yPos = 0; while (element) { if (element.tagName == "BODY") { var xScroll = element.scrollLeft || document.documentElement.scrollLeft; var yScroll = element.scrollTop || document.documentElement.scrollTop; xPos += (element.offsetLeft - xScroll + element.clientLeft); yPos += (element.offsetTop - yScroll + element.clientTop); } else { xPos += (element.offsetLeft - element.scrollLeft + element.clientLeft); yPos += (element.offsetTop - element.scrollTop + element.clientTop); } element = element.offsetParent; } return { x: xPos, y: yPos }; }

4.8K

Сегодня мы с помощью canvas JavaScript нарисуем круг, который будет двигаться вслед за курсором мыши.

Основной подход

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


Надпись на картинке: Круг нарисован без соблюдения масштаба.

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


Этот пример не такой уж сложный, чтобы разобраться в нем. Но это не значит, что нет ничего интересного “за кулисами ”. Далее при рассмотрении JavaScript мы коснемся некоторых важных аспектов приведенного кода, которые вы, возможно, не поняли. С чего начать работу?

Первое, что нам нужно — это страница, готовая к работе с canvas JavaScript . Если у вас еще нет ее, то поместите следующий код в пустую HTML-страницу :

Canvas Follow Mouse canvas { border: #333 10px solid; } body { padding: 50px; } var canvas = document.querySelector("#myCanvas"); var context = canvas.getContext("2d");

Рисуем круг

Первое, что мы собираемся сделать, это нарисовать круг. Внутри тега добавьте следующий код после строки с переменной context :

function update() { context.beginPath(); context.arc(100, 100, 50, 0, 2 * Math.PI, true); context.fillStyle = "#FF6A6A"; context.fill(); } update();

Приведенный выше скрипт определяет функцию update , которая содержит код для прорисовки круга. Обратите внимание, что мы не только определяем функцию update , но и вызываем ее. Если вы просмотрите свою страницу в браузере, то увидите следующее:


Наш круг имеет радиус 50 пикселей и расположен в точке (100, 100 ). На данный момент, мы собираемся сохранить позицию круга фиксированной. Это не надолго, до тех пор пока мы не получим позицию курсора мыши в canvas с помощью методов JavaScript ! Получение позиции курсора мыши

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

Прослушивание события мыши

Давайте посмотрим на код для первой части. Добавьте следующий код чуть выше функции update :

var mouseX = 0; var mouseY = 0; canvas.addEventListener("mousemove", setMousePosition, false); function setMousePosition(e) { mouseX = e.clientX; mouseY = e.clientY; }

Этот код выполняет довольно простые вещи. Мы отслеживаем событие MouseMove на холсте. И когда это событие наступает, мы вызываем обработчик события setMousePosition :

function setMousePosition(e) { mouseX = e.clientX; mouseY = e.clientY; }

Функция setMousePosition присваивает текущее горизонтальное и вертикальное положение курсора мыши свойствам mouseX и mouseY . Она делает это, опираясь на свойства clientX и clientY , предоставляемые MouseEvent .

Получение точных координат курсора мыши

Позиция курсора мыши, сохраненная в свойствах mouseX и mouseY , по умолчанию содержит координаты, заданные относительно левого верхнего угла окна браузера. Значения координат курсора мыши, которые мы имеем сейчас, неточные, так как не учитывает положение элемента canvas JavaScript .

Чтобы исправить это, у нас есть функция GetPosition :

function getPosition(el) { var xPosition = 0; var yPosition = 0; while (el) { xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft); yPosition += (el.offsetTop - el.scrollTop + el.clientTop); el = el.offsetParent; } return { x: xPosition, y: yPosition }; }

Добавьте эту функцию в нижнюю часть кода, ниже функции update .

Эта функция применяется для передачи интересующей нас позиции в элемент. Затем она возвращает объект, содержащий координаты х и у . Мы воспользуемся этой функцией, чтобы выяснить, где на странице находится элемент canvas JavaScript , а затем скорректируем значения mouseX и mouseY .

Чтобы использовать функцию GetPosition и исправить значения mouseX и mouseY , внесите следующие дополнения и изменения, которые я выделил:

var canvasPos = getPosition(canvas); var mouseX = 0; var mouseY = 0; canvas.addEventListener("mousemove", setMousePosition, false); function setMousePosition(e) { mouseX = e.clientX - canvasPos.x; mouseY = e.clientY - canvasPos.y; }

Теперь переменная CanvasPos хранит позицию, которую возвращает функция GetPosition . В обработчике событий setMousePosition мы используем значения х и у , возвращенные из canvasPos для коррекции значения, сохраненного с помощью переменных mouseX и mouseY .

Перемещение круга

В предыдущем разделе мы получили код курсора мыши и переменные mouseX и mouseY , хранящие текущее положение курсора мыши на холсте. Остается только подключить эти значения к нашему коду прорисовки круга внутри функции update, чтобы получить положение круга, отражающее позицию курсора мыши для рисования на canvas JavaScript .

Для начала преобразуем функцию update в объект обратного вызова requestAnimationFrame . Благодаря этому функция синхронизируется со скоростью рисования браузера (около 60 раз в секунду ). Добавьте следующую выделенную строку в нижней части функции update :

function update() { context.beginPath(); context.arc(100, 100, 50, 0, 2 * Math.PI, true); context.fillStyle = "#FF6A6A"; context.fill(); requestAnimationFrame(update); } update();

Теперь нам нужно обновить код прорисовки круга, чтобы использовать значения mouseX и mouseY вместо фиксированной позиции (100, 100 ), которую мы определили первоначально. Внесите в выделенную строку следующие изменения:

function update() { context.beginPath(); context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true); context.fillStyle = "#FF6A6A"; context.fill(); requestAnimationFrame(update); } update();

После этого сохраните HTML-документ и просмотрите его в браузере. Посмотрите, что происходит при перемещении мыши по canvas JavaScript . Теперь наш круг повсюду будет следовать за курсором мыши:


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

Чтобы сделать это, добавьте следующую выделенную строку кода в функцию update :

unction update() { context.clearRect(0, 0, canvas.width, canvas.height); context.beginPath(); context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true); context.fillStyle = "#FF6A6A"; context.fill(); requestAnimationFrame(update); }

context.clearRect (0, 0, canvas.width, canvas.height);

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

Зачем использовать requestAnimationFrame?

Весь код, связанный с рисунком, находится внутри функции update , которая циклично выполняет функцию requestAnimationFrame . Никакой анимации здесь нет. Мы просто перемещаем курсор мыши и обновляем позицию круга только, когда курсор перемещается. Учитывая все это, почему не весь код прорисовки присутствует в обработчике события MouseMove ? Это выглядело бы примерно так:

canvas.addEventListener("mousemove", setMousePosition, false); function setMousePosition(e) { mouseX = e.clientX - canvasPos.x; mouseY = e.clientY - canvasPos.y; context.clearRect(0, 0, canvas.width, canvas.height); context.beginPath(); context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true); context.fillStyle = "#FF6A6A"; context.fill(); }

Если вы смогли сделать это изменение (и полностью избавились от функции update ), наш пример все равно будет продолжать работать. Наш пример может работать так же, как requestAnimationFrame .

Причина кроется в помогающем нам браузере, который не делает лишнюю работу и выполняет «правильные » вещи, так как наша конечная цель. Когда дело доходит до рисования на canvas JavaScript , мы хотим быть синхронными с браузером в тот момент, когда он готов к прорисовке пикселей.

Событие MouseMove не имеет представления о том, когда браузер готов отобразить что-то на экране, так что наш обработчик события будет стараться заставить браузер нарисовать что-то на экране. Единственный способ избежать этого — использовать функцию requestAnimationFrame .

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

Вывод

Все это начиналось, как простой эффект. Теперь у нас есть круг, который следует за курсором мыши.

Данная публикация представляет собой перевод статьи «Follow the Mouse Cursor » , подготовленной дружной командой проекта

  • Перевод

Автор статьи, перевод которой мы публикуем, предлагает поговорить о решении задач из сферы компьютерного зрения исключительно средствами веб-браузера. Решить подобную задачу не так уж и трудно благодаря JavaScript-библиотеке TensorFlow . Вместо того, чтобы обучать собственную модель и предлагать её пользователям в составе готового продукта, мы дадим им возможность самостоятельно собрать данные и обучить модель прямо в браузере, на собственном компьютере. При таком подходе серверная обработка данных совершенно не нужна.


Испытать то, созданию чего посвящён этот материал, можно . Вам для этого понадобится современный браузер, веб-камера и мышь. Вот исходный код проекта. Он не рассчитан на работу на мобильных устройствах, автор материала говорит, что у него не было времени на соответствующие доработки. Кроме того, он отмечает, что рассматриваемая тут задача усложнится в том случае, если придётся обрабатывать видеопоток с движущейся камеры.

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

В браузере очень легко получить доступ к веб-камере. Если предположить, что в качестве входных данных для нейронной сети будет использоваться всё изображение с камеры, то можно сказать, что оно для этих целей слишком велико. Системе придётся проделать большую работу только для того, чтобы определить то место на изображении, где находятся глаза. Такой подход может хорошо показать себя в том случае, если речь идёт о модели, которую разработчик обучает самостоятельно и развёртывает на сервере, однако если мы говорим об обучении и использовании модели в браузере - это уже чересчур.

Для того чтобы облегчить задачу сети, мы можем предоставить ей лишь часть изображения - ту, которая содержит глаза пользователя и небольшую область вокруг них. Эту область, представляющую собой прямоугольник, окружающий глаза, можно выявить с помощью сторонней библиотеки. Поэтому первая часть нашей работы выглядит так:


Входные данные с веб-камеры, распознавание лица, обнаружение глаз, обрезанное изображение

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

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


Входное изображение, модель - свёрточная нейронная сеть, координаты, предсказанное сетью место на странице, куда смотрит пользователь.

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

Подготовка Для начала загрузим clmtrackr.js из соответствующего репозитория . Работу начнём с пустого HTML-файла, в котором импортируются jQuery, TensorFlow.js, clmtrackr.js и файл main.js с нашим кодом, над которым мы будем работать немного позже:

Получение видеопотока с веб-камеры Для того чтобы активировать веб-камеру и вывести видеопоток на страницу нам понадобится получить разрешение пользователя. Здесь я не привожу код, решающий проблемы совместимости проекта с различными браузерами. Мы будем исходить из предположения, в соответствии с которым наши пользователи работают в интернете с применением последней версии Google Chrome.

Добавим в HTML-файл следующий код. Он должен располагаться в пределах тега , но выше тегов :


Теперь поработаем с файлом main.js:

$(document).ready(function() { const video = $("#webcam"); function onStreaming(stream) { video.srcObject = stream; } navigator.mediaDevices.getUserMedia({ video: true }).then(onStreaming); });
Попробуйте этот код у себя. Когда вы откроете страницу, браузер должен запросить разрешение, а затем на экране появится картинка с веб-камеры.

Позже мы расширим код функции onStreaming() .

Поиск лица Теперь давайте воспользуемся библиотекой clmtrackr.js для поиска лица на видео. Для начала инициализируем систему слежения за лицом, добавив следующий код после const video = ... :

Const ctrack = new clm.tracker(); ctrack.init();
Теперь, в функции onStreaming() , мы подключаем систему поиска лица, добавляя туда следующую команду:

Ctrack.start(video);
Это всё, что нам нужно. Теперь система сможет распознать лицо в видеопотоке.

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

#webcam, #overlay { position: absolute; top: 0; left: 0; }
Если хотите - можете переместить встроенный стиль в отдельный CSS-файл.

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

Вот код, реализующий то, о чём мы только что говорили. Добавить его надо ниже команды ctrack.init() :

Const overlay = $("#overlay"); const overlayCC = overlay.getContext("2d"); function trackingLoop() { // Проверим, обнаружено ли в видеопотоке лицо, // и если это так - начнём его отслеживать. requestAnimationFrame(trackingLoop); let currentPosition = ctrack.getCurrentPosition(); overlayCC.clearRect(0, 0, 400, 300); if (currentPosition) { ctrack.draw(overlay); } }
Теперь вызовем функцию trackingLoop() в функции onStreaming() сразу после ctrack.start() . Эта функция будет сама планировать собственный перезапуск в каждом кадре.

Обновите страницу и посмотрите в веб-камеру. Вы должны увидеть зелёную «маску» вокруг лица в окне видео. Иногда для того чтобы система правильно распознала лицо, нужно немного подвигать головой в кадре.


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

К счастью, cmltracker даёт нам не только сведения о расположении лица, но и 70 контрольных точек. Если взглянуть на документацию к cmltracker, можно выбрать именно те контрольные точки, которые нам нужны.


Контрольные точки

Решим, что глаза - это прямоугольная часть изображения, границы которой касаются точек 23, 28, 24 и 26, расширенная на 5 пикселей в каждом направлении. Этот прямоугольник должен включать в себя всё, что для нас важно, если только пользователь не слишком сильно наклоняет голову.

Теперь, прежде чем мы сможем воспользоваться этим фрагментом изображения, нам нужен ещё один элемент для его вывода. Его размеры будут равны 50x25 пикселей. Прямоугольник с глазами будет вписан в этот элемент. Небольшие деформации изображения - это не проблема.

Добавьте в HTML-файл этот код, описывающий элемент , в который попадёт та часть изображения, на которой имеются глаза:

#eyes { position: absolute; top: 0; right: 0; }
Следующая функция вернёт координаты x и y , а также ширину и высоту прямоугольника, окружающего глаза. Она, в качестве входных данных, принимает массив positions , полученный от clmtrackr. Обратите внимание на то, что каждая координата, полученная от clmtrackr, имеет компоненты x и y . Эту функцию надо добавить в main.js:

Function getEyesRectangle(positions) { const minX = positions - 5; const maxX = positions + 5; const minY = positions - 5; const maxY = positions + 5; const width = maxX - minX; const height = maxY - minY; return ; }
Теперь, в каждом кадре, мы собираемся извлекать из видеопотока прямоугольник с глазами, обводить его красной линией на элементе , который наложен на элемент , а затем копировать его в новый элемент . Обратите внимание на то, что для того, чтобы правильно выявить нужную нам область, мы будем рассчитывать показатели resizeFactorX и resizeFactorY .

Замените следующим кодом блок if в функции trackingLoop() :

If (currentPosition) { // Выведем линии, проведённые между контрольными точками // на элементе , наложенном на элемент ctrack.draw(overlay); // Получим прямоугольник, ограничивающий глаза, и обведём его // красными линиями const eyesRect = getEyesRectangle(currentPosition); overlayCC.strokeStyle = "red"; overlayCC.strokeRect(eyesRect, eyesRect, eyesRect, eyesRect); // Видеопоток может иметь особые внутренние параметры, // поэтому нам нужны эти константы для перемасштабирования // прямоугольника с глазами перед обрезкой const resizeFactorX = video.videoWidth / video.width; const resizeFactorY = video.videoHeight / video.height; // Вырезаем прямоугольник с глазами из видео и выводим его // в соответствующем элементе const eyesCanvas = $("#eyes"); const eyesCC = eyesCanvas.getContext("2d"); eyesCC.drawImage(video, eyesRect * resizeFactorX, eyesRect * resizeFactorY, eyesRect * resizeFactorX, eyesRect * resizeFactorY, 0, 0, eyesCanvas.width, eyesCanvas.height); }
Перезагрузив теперь страницу, вы должны увидеть красный прямоугольник вокруг глаз, а то, что содержит этот прямоугольник - в соответствующем элементе . Если ваши глаза больше моих - поэкспериментируйте с функцией getEyeRectangle .


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

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

▍Отслеживание перемещений мыши Для того чтобы узнать, где именно на веб-странице расположен указатель мыши, нам понадобится обработчик события document.onmousemove . Наша функция, кроме того, нормализует координаты таким образом, чтобы они укладывались в диапазон [-1, 1]:

// Отслеживание перемещений мыши: const mouse = { x: 0, y: 0, handleMouseMove: function(event) { // Получим позицию указателя и нормализуем её, приведя к диапазону [-1, 1] mouse.x = (event.clientX / $(window).width()) * 2 - 1; mouse.y = (event.clientY / $(window).height()) * 2 - 1; }, } document.onmousemove = mouse.handleMouseMove;

▍Захват изображений Для захвата изображения, выводимого элементом и сохранения его в виде тензора, TensorFlow.js предлагает вспомогательную функцию tf.fromPixels() . Используем её для сохранения и последующей нормализации изображения с элемента , выводящего прямоугольник, содержащий глаза пользователя:

Function getImage() { // Захват текущего изображения в виде тензора return tf.tidy(function() { const image = tf.fromPixels($("#eyes")); // Добавление измерения: const batchedImage = image.expandDims(0); // Нормализация и возврат данных: return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1)); }); }
Обратите внимание на то, что функция tf.tidy() используется для того, чтобы навести порядок после завершения работы.

Мы могли бы просто сохранить все образцы в одной большой обучающей выборке, однако в машинном обучении важно проверять качество обучения модели. Именно поэтому нам надо сохранить некоторые образцы в отдельной контрольной выборке. После этого мы можем проверить поведение модели на новых для неё данных и узнать, не произошло ли чрезмерного обучения модели. Для этой цели 20% от общего количества образцов включены в контрольную выборку.

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

Const dataset = { train: { n: 0, x: null, y: null, }, val: { n: 0, x: null, y: null, }, } function captureExample() { // Возьмём самое свежее изображение глаз и добавим его в набор данных tf.tidy(function() { const image = getImage(); const mousePos = tf.tensor1d().expandDims(0); // Решим, в какую выборку (обучающую или контрольную) его добавлять const subset = dataset; if (subset.x == null) { // Создадим новые тензоры subset.x = tf.keep(image); subset.y = tf.keep(mousePos); } else { // Конкатенируем их с существующими тензорами const oldX = subset.x; const oldY = subset.y; subset.x = tf.keep(oldX.concat(image, 0)); subset.y = tf.keep(oldY.concat(mousePos, 0)); } // Увеличим счётчик subset.n += 1; }); }
И, наконец, нам надо привязать эту функцию к клавише Пробел:

$("body").keyup(function(event) { // Выполняется при нажатии на клавишу Пробел на клавиатуре if (event.keyCode == 32) { captureExample(); event.preventDefault(); return false; } });
Теперь каждый раз, когда нажимают на клавишу Пробел, изображение глаз и координаты указателя мыши добавляются в один из наборов данных.

Обучение модели Создадим простую свёрточную нейронную сеть. TensorFlow.js предоставляет для этой цели API, напоминающее Keras. У сети должен быть слой conv2d , слой maxPooling2d , и, наконец, слой dense c двумя выходными значениями (они представляют экранные координаты). Попутно я добавил в сеть, в качестве регуляризатора, слой dropout , и слой flatten для того, чтобы преобразовать двухмерные данные в одномерные. Обучение сети выполняется с помощью оптимизатора Adam.

Обратите внимание на то, что я остановился на использованных здесь параметрах сети после экспериментов на моём MacBook Air. Вы вполне можете подобрать собственную конфигурацию модели.

Вот код модели:

Let currentModel; function createModel() { const model = tf.sequential(); model.add(tf.layers.conv2d({ kernelSize: 5, filters: 20, strides: 1, activation: "relu", inputShape: [$("#eyes").height(), $("#eyes").width(), 3], })); model.add(tf.layers.maxPooling2d({ poolSize: , strides: , })); model.add(tf.layers.flatten()); model.add(tf.layers.dropout(0.2)); // Два выходных значения x и y model.add(tf.layers.dense({ units: 2, activation: "tanh", })); // Используем оптимизатор Adam с коэффициентом скорости обучения 0.0005 и с функцией потерь MSE model.compile({ optimizer: tf.train.adam(0.0005), loss: "meanSquaredError", }); return model; }
Прежде чем приступать к обучению сети, мы задаём фиксированное количество эпох и переменный размер пакета (так как мы, возможно, будем работать с очень маленькими наборами данных).

Function fitModel() { let batchSize = Math.floor(dataset.train.n * 0.1); if (batchSize < 4) { batchSize = 4; } else if (batchSize > 64) { batchSize = 64; } if (currentModel == null) { currentModel = createModel(); } currentModel.fit(dataset.train.x, dataset.train.y, { batchSize: batchSize, epochs: 20, shuffle: true, validationData: , }); }
Теперь добавим на страницу кнопку для запуска обучения. Этот код идёт в HTML-файл:

Train! #train { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24pt; }
Этот код надо добавить в JS-файл:

$("#train").click(function() { fitModel(); });

Куда смотрит пользователь? Теперь, когда мы можем собирать данные и подготовили модель, можно начать предсказывать место на странице, куда смотрит пользователь. Укажем на это место с помощью зелёного кружка, который перемещается по экрану.

Сначала добавим на страницу кружок:

#target { background-color: lightgreen; position: absolute; border-radius: 50%; height: 40px; width: 40px; transition: all 0.1s ease; box-shadow: 0 0 20px 10px white; border: 4px solid rgba(0,0,0,0.5); }
Для того чтобы перемещать его по странице, мы периодически передаём текущее изображение глаз нейронной сети и задаём ей вопрос о том, куда смотрит пользователь. Модель в ответ выдаёт две координаты, по которым должен быть перемещён кружок:

Function moveTarget() { if (currentModel == null) { return; } tf.tidy(function() { const image = getImage(); const prediction = currentModel.predict(image); // Конвертируем нормализованные координаты в позицию на экране const targetWidth = $("#target").outerWidth(); const targetHeight = $("#target").outerHeight(); const x = (prediction.get(0, 0) + 1) / 2 * ($(window).width() - targetWidth); const y = (prediction.get(0, 1) + 1) / 2 * ($(window).height() - targetHeight); // Переместим в нужное место кружок: const $target = $("#target"); $target.css("left", x + "px"); $target.css("top", y + "px"); }); } setInterval(moveTarget, 100);
Я установил интервал на 100 миллисекунд. Если ваш компьютер не такой мощный, как мой, возможно, вы решите его увеличить.

Итоги Теперь у нас готово всё, что нужно для реализации идеи, изложенной в самом начале этого материала. Испытайте то, что у нас получилось. Подвигайте курсором мыши, следя за ним глазами, и понажимайте клавишу Пробел. Потом нажмите кнопку запуска обучения.

Соберите ещё данных, нажмите кнопку ещё раз. Через некоторое время зелёный кружок начнёт передвигаться по экрану вслед за вашим взглядом. Поначалу он будет не особенно хорошо попадать в то место, куда вы смотрите, но, начиная с примерно 50 собранных образцов, после нескольких этапов обучения, и если вам будет сопутствовать удача, он будет довольно точно перемещаться в ту точку страницы, на которую вы смотрите. Полный код разобранного в этом материале примера можно найти

В этой статье будет показана первая анимация, а в следующей расскажу уже о второй. Там будет указатель в виде компаса. Взял я скрипт, немножко его подправил и изменил принцип вывода изображения. Так же вместо кота смотрящего в бинокль, я добавил известного котика - Саймона из мультфильма Simon"s Cat . Как и в исходнике предлагаю использовать данный скрипт на страницах типа 404. Конечно же все зависит от Вашей фантазии. Можете приспособить его для каких нибудь других целей. То что выйдет после установки себе на сайт данного материала, можете посмотреть ниже в примере, а также скачать исходники.

Понравилось? Если да, то приступим к описанию, как это работает и как добавить на свой сайт.

В первую очередь, давайте добавим HTML разметку нашей анимации.

По разметке, все просто. Имеем родительский блок с id - cat . Внутри него два блока отвечающие за глаза, внутри которых блоки отвечающие за зрачки. Получается мы определяем всего нашего кота, потом две зоны, а именно зоны глаз, внутри которых и будут двигаться зрачки. Данный код добавляете в то место сайта где хотите увидеть картинку с анимацией.

Следующим этапом будет добавление стилей, для корректного отображения будущей анимации.

#cat{ position: relative; width: 100%; } #cat:before{ content:""; width: 587px; background: url(nofound.png) center top no-repeat; height:410px; position: absolute; left:50%; margin-left:-293px; } .cat-eye{ position: absolute; display: none; width: 60px; height: 61px; } .cat-eyeball{ position: absolute; left: 22.5px; top: 24.5px; width: 16px; height: 16px; background: url(eyes.png); } #cat-eye-left { top: 82px; left: 50%; margin-left:-75px; } #cat-eye-right { top: 82px; left: 50%; margin-left:-5px; }

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

Важно обратить внимание, что фон с котом задается псевдоэлементу :before для блока #cat. Следует, так же правильно указать путь к изображению. Чтобы получить все изображения - скачайте исходники по ссылке выше. Теперь по глазам и зрачкам.

  • #cat - Основной блок, область, где будет картинка и анимация глаз.
  • #cat:before - Псевдоэлемент для добавления общего фона. В данном случаи кот Саймон. Можете добавить другую картинку, но тогда следует правильно разместить область глаз на ней.
  • .cat-eye - Это общий класс для двух глаз (не картинка, а именно область для зрачков). Задается размер по которому будет бегать зрачок. В данной ситуации -60x61px
  • .cat-eyeball - Уже сами зрачки. Задаем положение относительно глаз, размер и самое главное указываем путь к картинке - eyes.png . Если картинки не лежат в той же папке что и стили, то укажите правильный путь.
  • #cat-eye-left и #cat-eye-right - ID для левого и правого глаза. Расположение зон для движения зрачков. Если поменяете основную картинку, то в этих параметрах нужно будет менять расположение относительно родительского блока - #cat

Теперь пришла очередь самого скрипта, но для его работы сначала нужна библиотека jQuery . Если она у Вас подключена, то можно пропустить этот шаг. В системах управления она есть по умолчанию, если сайт самописный то нужно смотреть. Если ее все же нет, то нужно добавить ее. делается это или в шапке или в подвале сайта, добавлением такой строки:

var el1 = $("#cat-eye-left"), eyeBall1 = el1.find("div"); var el2 = $("#cat-eye-right"), eyeBall2 = el2.find("div"); el1.show(); el2.show(); var x1 = el1.offset().left + 37, y1 = el1.offset().top + 25; var r = 10, x , y, x2, y2, isEyeProcessed = false; $("html").mousemove(function(e) { if (!isEyeProcessed){ isEyeProcessed = true; var x2 = e.pageX, y2 = e.pageY; y = ((r * (y2 - y1)) / Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))) + y1; x = (((y - y1) * (x2 - x1)) / (y2 - y1)) + x1; eyeBall1.css({ marginTop: (y - y1 + 1) + "px", marginLeft: (x - x1) + "px"}); eyeBall2.css({ marginTop: (y - y1 + 1) + "px", marginLeft: (x - x1) + "px"}); isEyeProcessed = false;}});

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

Второе - это область слежения за курсором. В данном скрипте в 7 строке указан - html . То есть по сути вся страница. Иногда используется body . Но если страница не полная и подвал не прижат к низу страницы, бывает что body лишь на пол страницы и выходя за его пределы, скрипт перестанет работать, так что лучше - html. Так же можно задать конкретный блок, внутри которого будет слежение за курсором, в то время, как на остальной странице это не будет происходить. В таком случаи вместо html нужно указать айди блок, например #block_name. Поместить HTML код из этой статьи внутрь блока с айди - #block_name и все.

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

На этом все, спасибо за внимание. 🙂

В статье представлены лучшие элементы пользовательского веб-интерфейса (UI) и анимации, которые были размещены на сайте CodePen. Все проекты были созданы с помощью CSS и JavaScript.

Разработанная Юлианом Гарнье (Julian Garnier) анимированная модель представляет восемь планет, вращающихся вокруг Солнца в 3D (Boffins недавно решил, что официально Плутон больше не является планетой). На создание этого демо автора вдохновили проекты Алекса Гирона (Alex Giron) и Николаса Гэлледжера (Nicolas Gallager).

Whale

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

Draggable overflow

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

Fluid grid using text align: justify Squishy buttons

Этот невероятный проект создает эффект псевдотактильного касания, даже если на кнопки воздействуют с помощью мыши.

Pure CSS peeling sticky

Эта CSS липучка вызывает приятное псевдоосязательное ощущение и представлена в виде метки, которую легко снять, раскрывая спрятанное под ней.

Color Smoke

Анимация цветных частиц, следующих за курсором мыши.

Эти шары боятся указателя мыши. Если вы перемещаете курсор агрессивно, они паникуют и рассредотачиваются, но если будете приближаться медленно, то они будут дрейфовать прочь с такой же скоростью, всегда держа определенную дистанцию. Если же вы удаляетесь, то они собираются вместе и заинтересованно и незаметно движутся к вам.

Tearable cloth