Flatiron Field Day - это проект, вдохновленный экспериментом Reddit r / place. Если вы не знакомы, r / place был холстом, который позволял пользователям рисовать все, что они хотят. Предостережение заключалось в том, что они могли рисовать только один пиксель за раз и только раз в десять минут или около того. Конечным результатом этого была серия альянсов, выстраивающих цвета, узоры или изображения, которые были важны для них, и пытались защитить их от людей, которые попытаются перезаписать.
В нашей версии участники могли рисовать пиксели до четырех раз в секунду, но у них было еще одно преимущество, которого не было у участников r / place - они могли использовать код для рисования. Учащиеся в нашей школе находятся на разных уровнях, и в зависимости от этого уровня они могут практиковать Ruby, JavaScript или Python, поэтому мы создали клиентов для каждого из этих языков. Мы также создали средство отслеживания статистики, которое обновляется в реальном времени, сервер для обработки всех этих запросов и программу, которую мы назвали The Nexus, чтобы отфильтровывать неверные запросы и ограничивать скорость, с которой студенты могли рисовать.
Последней частью головоломки был клиент Canvas, который был моей областью. Когда я начал, меня ждала альфа-версия клиента, но в ней не было тонны функциональности. Он успешно отображал плитки, которые уже были установлены, и пользователи могли прыгать в консоль и программно устанавливать плитки, но это было некрасиво и неудобно для пользователя. Что еще более важно, пользоваться им было неинтересно!
У меня было несколько целей, связанных с этим обновлением клиента Canvas, но общая директива заключалась в том, чтобы «сделать это круто, сделать это весело, сделать это сексуально».
- A user should be able to drag the canvas around the screen - A user should be able to move the canvas with arrow keys - A user should be able to zoom the canvas - A user should be able to select a color - A user should be able to click a pixel to paint it
Масштаб холста
cycleZoom(e) { e.preventDefault() switch (this.zoom) { case 1: this.zoom = 2 this.zoomStatus.innerText = `x2` break; case 2: this.zoom = 4 this.zoomStatus.innerText = `x4` break; case 4: this.zoom = 8 this.zoomStatus.innerText = `x8` break; case 8: this.zoom = 16 this.zoomStatus.innerText = `x16` break; case 16: this.zoom = 1 this.zoomStatus.innerText = `x1` break; default: this.zoom = 1 this.zoomStatus.innerText = `x1` } this.zoomer.style.transform = `scale(${this.zoomMult(this.zoom)}, ${this.zoomMult(this.zoom)})` }
Я черпал вдохновение для нашей функции масштабирования холста из блога разработчиков r / place. Это был потрясающий ресурс и в целом отличное чтение. Я взял много информации из блога о преобразованиях CSS, которые были моим основным способом манипуляции во всем.
Мы использовали холст 500 x 500 и решили реализовать различные уровни масштабирования, чтобы наши пользователи могли быть настолько близки, насколько они считали полезными для них. Для этого мы обернули холст в div, который мы назвали zoomer, и все это контролируется нашим классом CanvasManager
.
Я прикрепил прослушиватель событий keyDown
к основной части сайта. Таким образом, каждый раз, когда кто-то нажимает пробел, этот слушатель запускает cycleZoom
в CanvasManager
. В свою очередь, cycleZoom
проверит текущий уровень масштабирования, а затем переключит нашего пользователя на следующий доступный уровень. С этим было довольно легко справиться, и все работало прекрасно, пока я не подключил свой компьютер к монитору 4K, на котором мы будем отображать доску во время Дня поля. На огромном экране холст выглядел крошечным.
Чтобы позаботиться об этом и учесть любое другое разрешение экрана, которое люди могут использовать, я создал формулу, которая будет регулироваться в зависимости от размера экрана. В этом случае переменная sizeAdjust
будет в 0,00165 раза больше высоты экрана. Умножение этого на текущий уровень масштабирования даст фактическое число, необходимое мне для правильной настройки масштаба.
Холст Движение
Чтобы движение работало, я использовал класс CanvasManager и новый класс Dragger. Это было бы лучше, если бы для перемещения использовался один класс, и так будет в будущем, но из-за нехватки времени вы можете писать глупые вещи.
Само сопротивление довольно стандартное, хотя математика движений может сбивать с толку. Я дал конструктору свойств Dragger startingX
и startingY
, а также lastTimeX
и lastTimeY
. Используя clientX
и clientY
, которые дает нам событие перемещения мыши, я вычислил, насколько пользователь переместил свою мышь, их начальную позицию и позицию остановки.
Я также добавил логическое значение, чтобы отслеживать, происходит ли событие перетаскивания. При нажатии мыши он переключается с false на true. Когда это правда, я бы преобразовал div в зависимости от движения пользователя.
mouseMove(e){ if (this.dragEvent === true){ this.xDiff = this.startingX - e.clientX this.yDiff = this.startingY - e.clientY this.newX = this.lastTimeX - this.xDiff this.newY = this.lastTimeY - this.yDiff const newPos = `translate(${this.newX}px, ${this.newY}px)` this.dragger.style.transform = newPos this.handleTilt(e) } }
Движение, основанное на вводе клавиш, было намного проще, просто перевод 20 пикселей в направлении, основанном на том, какую клавишу нажимает пользователь. Чтобы при увеличении масштаба ввод с помощью клавиш оставался жизнеспособным, я умножил перемещение на текущую величину масштабирования.
Выбор цвета / Покраска
Это было потрясающе. Я начал с создания тупой версии селектора цвета на странице. Я сделал несколько забавных новых значков (пипетку и кисть) для курсора, которые, как я думал, были просто для стиля, но в итоге оказались полезными и для логики.
По сути, на холсте был прослушиватель событий щелчка. Если бы курсор был пипеткой, я бы переключил курсор обратно в нормальное состояние и изменил цвет фона блока выбора цвета на цвет плитки, на которой щелкнул курсор. Достаточно легко получить значения RGB из определенной координаты с помощью getImageData
, если у вас есть значения X и Y. И, используя clientX
и clientY
вместе с величиной масштабирования и множителем масштабирования, я смог определить точное положение мыши, даже когда пользователь был увеличен.
Если бы курсор был кистью, я бы снова взял эти X и Y, взял цвет фона поля выбора цвета, а затем сделал бы запрос на публикацию на наш сервер, используя всю эту информацию.
Делаем это сексуально
Базовая функциональность была реализована за день, так что я немного повеселился. Я мечтал, чтобы холст наклонялся в зависимости от направления, в котором его тащили, что в итоге оказалось проще, чем я ожидал, - как только я ушел со своего пути.
Поскольку я думал, что функция наклона может быть немного выше моих возможностей, я начал с поиска библиотеки, которая справилась бы с этим за меня. В итоге я остановился на vanilla-tilt, интересной библиотеке, которая создает эффект трехмерного наклона при наведении курсора на элемент. Его легко реализовать, и казалось, что адаптировать его к моим потребностям будет несложно.
Я провел много времени, играя в код vanilla-tilt, пытаясь подогнать его под свои нужды. После достаточного количества времени, потраченного на копание в коде библиотеки, я понял, как они достигают своего эффекта наклона, и решил просто создать свой собственный.
Для этого вернемся к преобразованиям CSS. Поскольку преобразования представляют собой строку, ими сложно манипулировать, но их легко заменить. Таким образом, я создал div для наклона по оси Y и отдельный для наклона по оси X. Приведенный ниже фрагмент является самой базовой версией, которую я построил, и только для движения по оси X.
handleTilt(e){ if (e.movementX < -5) { this.Ytilter.style.transform = "perspective(800px) rotateY(-3deg)" } else if (e.movementX > 5) { this.Ytilter.style.transform = "perspective(800px) rotateY(3deg)" } else { this.Ytilter.style.transform = "perspective(800px) rotateY(0deg)" } }
e.movementX
и представляют расстояние, на которое указатель мыши переместился между его последним событием и текущим моментом. Если для базового порога не установлено значение 5, доска будет наклоняться, когда пользователи не собираются этого делать, так как слегка неаккуратный щелчок по-прежнему может указывать на небольшое движение.
Поигравшись с этим основным движением, я решил, что для этого нужны две вещи. Во-первых, нужна плавная анимация. Наклон работал, но был невероятно резким, когда менял направление. Во-вторых, я хотел, чтобы он реагировал на разные уровни ввода. Более быстрое движение мыши должно больше наклонять холст. Похоже, пользователи этого и ожидали, к тому же это весело.
if (e.movementX < -150) { this.Ytilter.style.transform = "perspective(800px) rotateY(-360deg)" }else if(e.movementX < -20){ this.Ytilter.style.transform = "perspective(800px) rotateY(-7deg)" }else if (e.movementX < -5) { this.Ytilter.style.transform = "perspective(800px) rotateY(-3deg)" }
Об этом позаботились несколько простых дополнений к handleTilt. Медленное движение мыши приводит к небольшому наклону, более быстрое движение мыши приводит к большему наклону, а очень быстрое движение мыши приводит к переворачиванию доски - просто пасхальное яйцо, чтобы люди продолжали исследовать.
#Ytilter { transition: all 0.15s; } #Xtilter{ transition: all 0.15s; }
Как оказалось, сгладить перекосы тоже было несложно. Все, что мне нужно было сделать, это добавить переходы к обоим наклонным div и настраивать их, пока я не найду золотую середину.
Подведение итогов
Глядя на код, и особенно когда вы пишете о нем, становится очевидно, что все это можно было бы сделать более аккуратно. Я мог бы лучше организовать свой код и использовать меньше div. Но что касается графика, с которым я столкнулся - я доволен тем, как все это работает. Ни у кого из наших участников не было проблем с холстом, и он работал безупречно весь день.
Я был всего лишь одним из членов большой команды, работающей над различными аспектами этого проекта, и каждый день, когда мы встречались, я был поражен, видя, какие новые вещи люди смогли собрать воедино. Я горжусь тем, что нам удалось собрать, и рад начать работу над следующей версией.
использованная литература
Хотите узнать больше о Дне поля? Посмотрите эту замечательную статью о том, как мы создали Nexus!
Https://medium.com/@sbal13/building-the-nexus-for-flatiron-field-day-72131c0942c1