Код JS (модуль jQuery)
"use strict";
/**
* Автор : ИНСИ
* Версия : 0.1.0
*
* Плагин : Рисует контур изображений (с прозрачным фоном) и вешает обработчика при наведении на контур изображения
*/
(function ($) {
/**
* Внутренний объект, с необходимым функционалом для работы модуля
*/
var LibOutLine = {
/**
* Конфиги по умолчанию
*/
Config: {
OutLine: {
Color: 'red',
Width: 3
},
Contour: {
Dx: [1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN],
Dy: [0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN]
},
Error: {
1: 'Необходимо обновить браузер, иначе модуль не сможет выполнить свои задачи',
2: 'Контур может быть высчитан только для картинки'
}
},
/**
* Текущие свойства
*/
Current: {
Id: 1
},
/**
* Имена ID, Class элементов
*/
ElName: {
Key: {
Map: 'map_',
Area: 'area_',
Canvas: 'canvas_',
Container: 'container_'
}
},
/**
* Возвращает стартовые координаты для замыкания
*
* @param {function} grid
*
* @returns {*[]}
*/
StartPoints: function (grid) {
// Объявление переменных стартовых координат X, Y
var x = 0, y = 0;
// Ищем стартовые координаты
while (true) {
// Если найдены стартовые координаты в сетке данных
if (grid(x, y)) {
// Возвращаем координаты
return [x, y];
}
// Если первый цикл
if (x === 0) {
// Определяем следуюшую координату X
x = y + 1;
// Определяем следуюшую координату Y
y = 0;
// Цикл не первый
} else {
// Определяем следуюшую координату X
x = x - 1;
// Определяем следуюшую координату Y
y = y + 1;
}
}
},
/**
* Возвращает контур картинки
*
* @param {function} grid
*
* @returns {{points: Array, stroke: Array}}
*/
OutLine: function (grid) {
// Массив с результатом
var result = {
// Массив точек
points: [],
// Массив с координатами
stroke: []
};
// Находим стартовые точки контура
var startPoints = this.StartPoints(grid);
// Запоминаем стартовые позиции
var x = startPoints[0], y = startPoints[1];
// Определяем необходимые переменные для высчитывание контура
var dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0;
// Высчитываем контур
do {
// Обнуляем позицию
i = 0;
// Определяем позицию
if (grid(x - 1, y - 1)) {
i += 1;
}
// Определяем позицию
if (grid(x, y - 1)) {
i += 2;
}
// Определяем позицию
if (grid(x - 1, y)) {
i += 4;
}
// Определяем позицию
if (grid(x, y)) {
i += 8;
}
// Определяем, является ли точка контуром
if (i === 6) {
dx = pdy === -1 ? -1 : 1;
dy = 0;
} else if (i === 9) {
dx = 0;
dy = pdx === 1 ? -1 : 1;
} else {
dx = this.Config.Contour.Dx[i];
dy = this.Config.Contour.Dy[i];
}
// Если точка является контуром
if (dx != pdx && dy != pdy) {
// Добавляем в результат
result.points.push([x, y]);
// Значение для координат
result.stroke.push(x + ',' + y);
// Сохраняем найденную позицию координаты X
pdx = dx;
// Сохраняем найденную позицию координаты Y
pdy = dy;
}
// Новое значение для координаты X
x += dx;
// Новое значение для координаты Y
y += dy;
// Пока не дойдем до стартовых точек
} while (startPoints[0] != x || startPoints[1] != y);
// Возвращаем результат
return result;
},
/**
* Рисует контур на теге <canvas />
*
* @param {object} canvasContext
* @param {object} points
*/
Draw: function (canvasContext, points) {
// Создаем новый холст для новой серии отрисовки
canvasContext.beginPath();
// Перемещаем "курсор" в позицию x, y
canvasContext.moveTo(points[0][0], points[0][1]);
// Обходим все точки
for (var i = 1; i < points.length; i++) {
// Рисуем контур
canvasContext.lineTo(points[i][0], points[i][1]);
}
// Завершаем задачу рисования
canvasContext.closePath();
// Рендерим
canvasContext.stroke();
},
/**
* Проверяет возможности браузера для высчитывания контура
*
* @returns {boolean}
*/
isSupported: function () {
// Создаем тег <canvas />
var canvas = document.createElement('canvas');
// Возвращаем результат
return !!(canvas.getContext && canvas.getContext('2d'));
}
};
/**
* Дополняем jQuery модулем .outline
*/
$.fn.outline = function (options, callBack) {
// Если первым параметром был передан callBack
if (typeof options === 'function') {
// Присваиваем callBack в "предназначенную" переменную :-)
callBack = options;
}
// Переменная с ошибкой
var err = false;
// Дожидаемся загрузки файла
this.on('load', function () {
// Объявляем необходимые данные
var container, canvas, mapTag, areaTag;
// Проверяем поддержку выполнение функции браузером
if (!LibOutLine.isSupported()) {
// Указываем описание ошибки
err = LibOutLine.Config.Error[1];
}
// Проверяем тип элемента
if ((err === false) && ($(this).is('img')) && (typeof $(this).attr('outLineId') !== 'number')) {
// Объявляем необходимые данные
var img = this, imgWidth = img.width, imgHeight = img.height, data, currentId = (++LibOutLine.Current.Id), keyMap = (LibOutLine.ElName.Key.Map + currentId);
// Добавляем атрибут usemap, для связи с тегом <map />
$(img)
// CSS свойства
.css('position', 'absolute')
// Указываем атрибуты
.attr({
// Ссылаемся на тег <map />
usemap: ('#' + keyMap),
// ID номер контура
outLineId: currentId
});
// Создаем контейнер
container = $('<span />')
// CSS свойства
.css({
position: 'relative'
})
// Добавляем в контейнер саму картинку
.append(img.outerHTML);
// Создаем тег <canvas />
canvas = $('<canvas />')
// Указываем атрибуты
.attr({
// Устанавливаем ширину
width: imgWidth,
// Устанавливаем высоту
height: imgHeight
})
// Указываем CSS свойства
.css({
display: 'none',
pointerEvents: 'none',
position: 'absolute',
zIndex: '999',
top: 0,
textAlign: 'center'
})
// Добавляем в контейнер
.appendTo(container);
// Получаем свойства
var canvasContext = $(canvas)[0].getContext("2d");
// Заново создаем изображение
canvasContext.drawImage(img, 0, 0);
// Получаем данные свойств изображения
data = (canvasContext.getImageData(0, 0, imgWidth, imgHeight)).data;
// Получаем контур картинки
var coords = LibOutLine.OutLine(function (x, y) {
return ((data[(y * imgWidth + x) * 4 + 3]) > 20);
});
// Добавляем тег <map /> в контейнер
mapTag = $('<map />')
// Указываем атрибуты
.attr({
// Ссылка на атрибут usemap у файла
name: keyMap
})
// Добавляем тег в контейнер
.appendTo(container);
// Если через опции передали слушателей на действия
if (typeof options === 'object' && typeof options.onEvent === 'object') {
// Вешаем слушателей
mapTag.on('click mouseenter mouseleave', function (e) {
// "Клик" по тегу <map />
if (e.type == 'click') {
// Если передали функцию
if (typeof options.onEvent.click === 'function') {
// Выполняем функцию и передаем все объекты
options.onEvent.click(err, container, canvas, mapTag, areaTag);
// Если при "клике" надо показать контур
} else if (options.onEvent.click === true) {
// Показываем тег <canvas />
canvas.css('display', 'inline-block');
// Если при "клике" надо убрать контур
} else if (options.onEvent.click === true) {
// Прячем тег <canvas />
canvas.hide();
}
// Курсор находится над контуром
} else if (e.type == 'mouseenter') {
// Если передали функцию
if (typeof options.onEvent.mouseenter === 'function') {
// Выполняем функцию и передаем все объекты
options.onEvent.mouseenter(err, container, canvas, mapTag, areaTag);
// Если при "клике" надо показать контур
} else if (options.onEvent.mouseenter == true) {
// Показываем тег <canvas />
canvas.css('display', 'inline-block');
// Если при "клике" надо убрать контур
} else if (options.onEvent.mouseenter == false) {
// Прячем тег <canvas />
canvas.hide();
}
// Курсор покинул контур
} else if (e.type == 'mouseleave') {
// Если передали функцию
if (typeof options.onEvent.mouseleave === 'function') {
// Выполняем функцию и передаем все объекты
options.onEvent.mouseleave(err, container, canvas, mapTag, areaTag);
// Если при "клике" надо показать контур
} else if (options.onEvent.mouseleave == true) {
// Показываем тег <canvas />
canvas.css('display', 'inline-block');
// Если при "клике" надо убрать контур
} else if (options.onEvent.mouseleave == false) {
// Прячем тег <canvas />
canvas.hide();
}
}
})
}
// Добавляем тег <area> в тег <map />
areaTag = $('<area />')
// Указываем атрибуты
.attr({
// Ссылка
href: "#",
// Тип области для Opera & Firefox
shape: "poly",
// Координаты контура
coords: coords.stroke
})
// Добавляем в тег <map>
.appendTo(mapTag);
// Очищаем канвас
canvasContext.clearRect(0, 0, imgWidth, imgHeight);
// Цвет обводки
canvasContext.strokeStyle = ((typeof options === 'object') && (typeof options.color === 'string') ? options.color : LibOutLine.Config.OutLine.Color);
// Количество PX для контура
canvasContext.lineWidth = ((typeof options === 'object') && (typeof options.width === 'number') ? options.width : LibOutLine.Config.OutLine.Width);
// Рисуем контур
LibOutLine.Draw(canvasContext, coords.points);
// Заменяем файл на контейнер
$(img).replaceWith(container);
// Элемент не является картинкой, либо ранее уже был высчитан контур
} else {
// Если ранее уже был определен контур
if (typeof $(this).attr('outLineId') === 'number') {
// Контейнер со всеми тегами
container = $(this).parent();
// Тег <canvas />
canvas = container.find('canvas');
// Тег <map /> ( с координатами контура )
mapTag = container.find('map');
// Тег <area />
container.find('area');
// Функция работает только с картинками
} else if ((err === false) && !($(this).is('img'))) {
// Указываем описание ошибки
err = LibOutLine.Config.Error[2];
}
}
// Если указан callBak
if (typeof callBack === 'function') {
// Вызываем callBack и передаем необходимые данные
callBack(err, container, canvas, mapTag, areaTag)
}
});
// Возвращаем объект обратно
return this;
};
})(jQuery);
HTML код
<img id="building" src="building.png"/>
Пример использования
$('#building').outline(
/**
* Входящие параметры
*
**/
{
// Цвет контура
color: 'red',
// Ширина контура
width: 1,
// Вывод контура на события
onEvent: {
// Клик по картинке
click: false,
// Курсор НАД картинкой
mouseenter: true,
// Курсор покинул область картинки
mouseleave: false
}
},
/**
* callBack после высчитывание контура
*
* @param {[bool]|[string]} err Ошибки
* @param {object} container Курсор на контейнер <span />
* @param {object} canvas Курсор на тег в контейнере <canvas />
* @param {object} mapTag Курсор на тег в контейнере <map />
* @param {object} areaTag Курсор на тег в контейнере <area />
*/
function (err, container, canvas, mapTag, areaTag) {
// Если произошла ошибка
if (err) {
// Выводим в консоль
console.log(err);
// Ошибок не было, можем начать управлять контуром
} else {
// Вешаем слушателей на тег <map> (слушателей можно указать другие, данные лишь для примера)
$(mapTag).on('click mouseenter mouseleave mousemove mousedown mouseup mousewheel', function (e) {
// Мышь над тегом <map />
if (e.type === 'mouseenter') {
// Показываем контур
$(canvas).css('display', 'inline-block');
// Мышь убрали с тега <map />
} else if (e.type === 'mouseleave') {
// Прячем контур
$(canvas).hide();
//
} else {
// ........ Под каждое действие можно создать свой функционал
}
})
}
}
);