Все статьи Кнопки и меню
Кнопка
Кнопка - интерактивный элемент с тремя состояниями. Первое - неактивна, второе - мышка занесена над кнопкой, третье - активное (мышка нажала на кнопку). Чтобы её реализовать, надо задать вопрос: что важно для кнопки?
- Координаты
- Размеры
- Спрайт
- Текущее состояние
Собрав требования, можно написать объект кнопки.
function Button(canvasContext)
{
this._state = Button.DEACTIVATED;
this._spriteLoaded = false;
this._x;
this._y;
this._width;
this._height;
this._sprite;
this._spriteWidth = 0;
this._spriteHeight = 0;
this.getState = function ()
{
return this._state;
};
this._draw = function ()
{
canvasContext.drawImage(that._sprite,
that._state * that._spriteWidth, 0, that._spriteWidth, that._spriteHeight,
that._x, that._y, that._width, that._height);
};
this._initView = function (view)
{
this._x = view.x;
this._y = view.y;
this._width = view.width;
this._height = view.height;
this._sprite = new Image();
this._sprite.src = view.sprite;
this._sprite.onload = function ()
{
that._spriteLoaded = true;
that._spriteWidth = that._sprite.width / 3; // state number
that._spriteHeight = that._sprite.height;
};
};
}
// Button тоже объект, поэтому ему можно задать свойства.
Button.DEACTIVATED = -1;
Button.INACTIVE = 0;
Button.HOVER = 1;
Button.ACTIVE = 2;
Можно приступать к обработке событий кнопки. Для их отслеживания необходимо иметь доступ к стандартным DOM событиям тега canvas. Кроме того, поскольку события из коробки работают только с DOM-узлами, их придётся пробрасывать через canvas, а кнопок может быть много, значит надо ввести id.
function Button(id, canvas, canvasContext)
{
//...
this._id = id;
this._getId = function ()
{
};
}
Напишем функцию, инициализирующую события.
this.activate = function (view)
{
this._state = Button.INACTIVE;
this._initView(view);
canvas.addEventListener("redraw", this._draw);
canvas.addEventListener("mousemove", function (event)
{
event.buttonState = Button.HOVER;
event.eventType = ":hover";
var click = this._getClickCoordinates(event);
if (this._InButton(click))
{
this._state = event.buttonState;
canvas.dispatchEvent(new Event(this._id + event.eventType));
}
event.buttonState = Button.INACTIVE;
var click = this._getClickCoordinates(event);
if (!this._InButton(click))
{
this._state = event.buttonState;
}
that._hoverHandler = this; // save function
});
canvas.addEventListener("mousedown", function (event)
{
event.buttonState = Button.ACTIVE;
event.eventType = ":down";
var click = this._getClickCoordinates(event);
if (this._InButton(click))
{
this._state = event.buttonState;
canvas.dispatchEvent(new Event(this._id + event.eventType));
}
that._downHandler = this; // save function
});
canvas.addEventListener("mouseup", function (event)
{
event.buttonState = Button.INACTIVE;
event.eventType = ":up";
var click = this._getClickCoordinates(event);
if (this._InButton(click))
{
this._state = event.buttonState;
canvas.dispatchEvent(new Event(this._id + event.eventType));
}
that._upHandler = this; // save function
});
};
Уберём дублирование.
this.activate = function (view)
{
this._state = Button.INACTIVE;
this._initView(view);
canvas.addEventListener("redraw", this._draw);
canvas.addEventListener("mousemove", function (event)
{
event.buttonState = Button.HOVER;
event.eventType = ":hover";
that._aboveEventHandle(event);
event.buttonState = Button.INACTIVE;
that._outEventHandle(event);
that._hoverHandler = this; // save function
});
canvas.addEventListener("mousedown", function (event)
{
event.buttonState = Button.ACTIVE;
event.eventType = ":down";
that._aboveEventHandle(event);
that._downHandler = this; // save function
});
canvas.addEventListener("mouseup", function (event)
{
event.buttonState = Button.INACTIVE;
event.eventType = ":up";
that._aboveEventHandle(event);
that._upHandler = this; // save function
});
};
this._aboveEventHandle = function (event)
{
var click = this._getClickCoordinates(event);
if (this._InButton(click))
{
this._state = event.buttonState;
canvas.dispatchEvent(new Event(this._id + event.eventType));
}
};
this._outEventHandle = function (event)
{
var click = this._getClickCoordinates(event);
if (!this._InButton(click))
{
this._state = event.buttonState;
}
};
Заметим, что для расчёта координат был использован метод _getClickCoordinates, так как свойства события event.clientX и event.clientY хранят координаты мыши относительно экрана, а не относительно документа.
this._getClickCoordinates = function (event)
{
var click = {};
click.x = event.pageX - canvas.getBoundingClientRect().left - window.scrollX;
click.y = event.pageY - canvas.getBoundingClientRect().top - window.scrollY;
return click;
};
Напишем метод, который деактивирует кнопку, уничтожающую обработчики событий.
this.deactivate = function ()
{
this._state = Button.DEACTIVATED;
canvas.removeEventListener("redraw", this._draw);
canvas.removeEventListener("mouseover", this._hoverHandler);
canvas.removeEventListener("mousedown", this._downHandler);
canvas.removeEventListener("mouseup", this._upHandler);
this._hoverHandler = undefined;
this._downHandler = undefined;
this._upHandler = undefined;
};
Осталась одна проблема: генерация уникального id, который бы не совпадал с другой кнопкой. Для этого следует написать контроллер.
Контроллер
Соберём требования для контроллера. Контроллер должен уметь:
- добавлять кнопку
- удалять кнопку
- генерировать уникальные id для создаваемых кнопок
Кроме того в будущем, возможно, помимо кнопки появятся другие элементы, вроде текстовых полей и форм ввода, поэтому надо придумать универсальный способ генерации id. Также стоит учесть, что операция выделения памяти в js - дорогостоящая операция, поэтому надо сократить количество создаваемых объектов до минимума. Вместо удаления кнопки можно помечать как неактивные, а после инициализировать новыми значениями.
Получился вот такой вот класс.
function ScreenController(canvas, canvasContext)
{
var DEACTIVATED = -1;
this._elements = [];
this.deleteElement = function (id)
{
this._elements[id].deactivate();
};
// element Button
this._buttons = [];
this.addButton = function (view)
{
var button = this._findFreeButton();
if (button == null)
{
button = this._createNewButton();
}
button.activate(view);
return button;
};
this._createNewButton = function ()
{
var id = this._elements.length;
this._elements[id] = new Button(id, canvas, canvasContext);
this._buttons[this._buttons.length] = id;
return this._elements[id];
};
this._findFreeButton = function ()
{
for (var i = 0; i < this._buttons.length; ++i)
{
var button = this._elements[this._buttons[i]];
if (button.getState() == DEACTIVATED)
{
return button;
}
}
return null;
};
// End element Button
}
Разберём его подробнее. Этот класс имеет свойство _elements, которое хранит ссылки на элементы. Как видно из метода _createNewButton, id - это не что иное, как номер элемента в массиве _elemnts. Для более удобной работы с кнопками, добавлено свойство _buttons, которое хранит все id уже созданных кнопок.
Опробуем класс.
var FIRST_BUTTON_VIEW = {
x: 200,
y: 100,
width: 300,
height: 60,
sprite: "img/sprite.png"
};
var SECOND_BUTTON_VIEW = {
x: 600,
y: 100,
width: 300,
height: 60,
sprite: "img/sprite.png"
};
function initTick(canvas, canvasContext)
{
animationTick();
function animationTick()
{
canvasContext.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
canvas.dispatchEvent(new Event("redraw"));
window.requestAnimationFrame(animationTick);
}
}
window.onload = function()
{
var canvas = document.getElementById("field");
var canvasContext = canvas.getContext("2d");
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
initTick(canvas, canvasContext);
var controller = new ScreenController(canvas, canvasContext);
var button1 = controller.addButton(FIRST_BUTTON_VIEW);
var button2 = controller.addButton(SECOND_BUTTON_VIEW);
canvas.addEventListener(button2.getId() + ":up", function ()
{
console.log("button2 up");
});
canvas.addEventListener(button2.getId() + ":down", function ()
{
console.log("button2 down");
});
canvas.addEventListener(button1.getId() + ":up", function ()
{
controller.deleteElement(button2.getId());
});
};