Три метода обнаружения мыши для холста HTML5, ни один из них - PullRequest
12 голосов
/ 18 октября 2011

Я создал библиотеку canvas для управления сценами фигур для некоторых рабочих проектов. Каждая фигура - это объект, с которым связан метод рисования. Во время обновления холста каждая фигура в стеке рисуется. Форма может иметь типичные связанные события мыши, которые обернуты вокруг собственных событий мыши DOM холста.

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

  1. Очищенный холст-призрак используется для самостоятельного рисования отдельной фигуры. Затем я сохраняю копию холста-призрака с getImageData(). Как вы можете себе представить, это занимает много памяти, когда есть много точек с привязанными событиями мыши (100 кликабельных фигур на холсте 960x800 - это ~ 300 МБ в памяти).

  2. Чтобы обойти проблему с памятью, я начал перебирать данные пикселей и хранить только адреса для пикселей с ненулевым альфа-каналом. Это хорошо сработало для уменьшения памяти, но резко увеличило загрузку процессора. Я выполняю итерации только по каждому 4-му индексу (RGBA), и любой пиксельный адрес с ненулевым альфа-каналом сохраняется в качестве хэш-ключа для быстрого поиска при перемещении мыши. Он по-прежнему перегружает мобильные браузеры и Firefox в Linux на 10+ секунд.

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

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

Я знаю, что это не новая проблема для разработчиков видеоигр, и для такого рода вещей должны быть быстрые алгоритмы. Если кто-то знает об алгоритме, который может (реально) разрешать сотни форм, не занимая ЦП более нескольких секунд или резко увеличивая потребление ОЗУ, я был бы очень признателен.

Есть две другие темы по переполнению стека при обнаружении наведения мыши, в обеих из которых обсуждается эта тема, но они не выходят за рамки 3 описанных мною методов. Обнаружение наведения мыши на определенные точки на холсте HTML? и Наведите курсор мыши на HTML5 canvas .

РЕДАКТИРОВАТЬ: 2011/10/21

Я протестировал другой метод, который более динамичен и не требует хранения чего-либо, но в Firefox он страдает от проблем с производительностью. Метод в основном состоит в том, чтобы зацикливать фигуры и: 1) очищать 1x1 пиксель под мышью, 2) рисовать фигуру, 3) получать 1x1 пиксель под мышью. Удивительно, но это очень хорошо работает в Chrome и IE, но с треском в Firefox.

Очевидно, что Chrome и IE способны оптимизировать, если вам нужна только небольшая область пикселей, но Firefox, кажется, не оптимизирует вообще, основываясь на требуемой области пикселей. Возможно, внутренне он получает весь холст, а затем возвращает вашу пиксельную область.

Код и необработанный вывод здесь: http://pastebin.com/aW3xr2eB.

Canvas getImageData() Performance (ms)

Ответы [ 2 ]

7 голосов
/ 21 октября 2011

Если я правильно понимаю вопрос, вы хотите определить, когда мышь вводит / оставляет форму на холсте, правильно?

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

Предполагая, что у вас есть какой-то список фигур, аналогичный тому, который описывает @Benjammmin ',Вы можете зациклить видимые фигуры и выполнить проверку точки внутри полигона:

// Track which shape is currently under the mouse cursor, and raise
// mouse enter/leave events
function trackHoverShape(mousePos) {
    var shape;
    for (var i = 0, len = visibleShapes.length; i < len; i++) {
        shape = visibleShapes[i];
        switch (shape.type ) {
            case 'arc':
                if (pointInCircle(mousePos, shape) &&
                    _currentHoverShape !== shape) {
                        raiseEvent(_currentHoverShape, 'mouseleave');
                        _currentHoverShape = shape;
                        raiseEvent(_currentHoverShape, 'mouseenter');
                    return;
                }
                break;
            case 'rect':
                if (pointInRect(mousePos, shape) &&
                    _currentHoverShape !== shape) {
                       raiseEvent(_currentHoverShape, 'mouseleave');
                       _currentHoverShape = shape;
                       raiseEvent(_currentHoverShape, 'mouseenter');
                }
                break;
        }
    }
}

function raiseEvent(shape, eventName) {
    var handler = shape.events[eventName];
    if (handler)
        handler();
}

// Check if the distance between the point and the shape's
// center is greater than the circle's radius. (Pythagorean theroem)
function pointInCircle(point, shape) {
    var distX = Math.abs(point.x - shape.center.x),
        distY = Math.abs(point.y - shape.center.y),
        dist = Math.sqrt(distX * distX + distY * distY);
    return dist < shape.radius;
}

Итак, просто вызовите trackHoverShape внутри события canvas mousemove, и оно будет отслеживать форму, находящуюся в данный момент подмышь.

Надеюсь, это поможет.

3 голосов
/ 18 октября 2011

Из комментария:

Лично я бы просто переключился на использование SVG.Это больше для чего это было сделано.Однако, возможно, стоит взглянуть на источник EaselJS .Есть метод Stage.getObjectUnderPoint (), и его демонстрация этого, кажется, работает отлично.

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

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

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

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

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

В качестве примера рассмотрим, что я нарисовал два объекта.Я буду хранить методы рисования прямоугольника и круга в удобочитаемой форме.

  • circ = ['beginPath', ['arc', 75, 75, 10], 'closePath', 'fill']
  • rect = ['beginPath', ['rect', 150, 5, 30, 40], 'closePath', 'fill']

(Вы можете захотетьчтобы минимизировать сохраненные данные или использовать другой синтаксис, такой как синтаксис SVG)

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

  • circ = {left: 65, top: 65, right: 85, bottom: 85}
  • rect = {left: 150, top: 5, right: 180, bottom: 45}

На холсте произошло событие щелчка.Точка мыши - {x: 70, y: 80}

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

Анализируя метод рисования кругов, мы можем воссоздать его на холсте-призраке и затем проверить, попадают ли координаты мыши в небелые пиксели.*

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

Как вы можете видеть, это устраняет необходимость хранить 960 x 800x 100 пикселей и только 960 x 800 x2 не более.

Эту идею лучше всего реализовать как своего рода API для автоматической обработки хранилища данных (например, метод рисования, размеры...).

...