В данном уроке мы сделаем искажающую сферу, которая перемещается по изображению в бесконечном цикле. Эффект можно использовать для фоновых изображений на сайте.
Разметка HTML
Для формирования эффекта создаем два объекта canvas : один для оригинального изображения, а другой - для искажающей сферы.
<div class="container">
<canvas id="slideshow" width="1024" height="630"></canvas>
<canvas id="obj" width="256" height="256"></canvas>
</div>
CSS
Объект для искаженной сферы должен находиться поверх оригинального изображения:
.container {
height: 630px;
margin: 50px auto;
position: relative;
width: 1024px;
z-index: 1;
}
#obj {
position: absolute;
z-index: 2;
}
JavaScript
При инициализации скрипт подготавливает два объекта canvas и два объекта context . Затем происходит загрузка фонового изображения и вывод его в первом контексте. Затем подготавливается хэш таблица для трансформаций сферы: aMap (с помощью функции mathTwirl). В конце запускается таймер, который обновляет сцену. Функция updateScene обновляет координаты объекта сферы и выводит ее во втором контексте.
var canvas, ctx;
var canvasObj, ctxObj;
var iDstW = 256;
var iDstH = 256;
var iXSpeed = 4;
var iYSpeed = 3;
var iLastX = iDstW / 2;
var iLastY = iDstH / 2;
var oImage;
var aMap = [];
var aMapT = [];
var aBitmap;
var mathTwirl = function(px,py) {
var x = px - iDstW / 2;
var y = py - iDstH / 2;
var r = Math.sqrt(x * x + y * y);
var maxR = iDstW / 2;
if (r > maxR) return {'x':px, 'y':py, 't': 1};
var a = Math.atan2(y, x);
a -= 1 - r / maxR;
var dx = Math.cos(a) * r;
var dy = Math.sin(a) * r;
return {'x': dx+iDstW/2, 'y': dy+iDstH/2, 't': 1.5}
}
window.onload = function(){
// Загружаем фоновое изображение
oImage = new Image();
oImage.src = 'images/bg.jpg';
oImage.onload = function () {
// Создаем объекты canvas и context objects
canvas = document.getElementById('slideshow');
ctx = canvas.getContext('2d');
canvasObj = document.getElementById('obj');
ctxObj = canvasObj.getContext('2d');
// Очищаем контекст
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Выводим исходное изобаржение
ctx.drawImage(oImage, 0, 0);
aBitmap = ctx.getImageData(0, 0, iDstW, iDstH);
for (var y = 0; y < iDstH; y++) {
for (var x = 0; x < iDstW; x++) {
var t = mathTwirl(x, y);
aMap[(x + y * iDstH) * 2 + 0] = Math.max(Math.min(t.x, iDstW - 1), 0);
aMap[(x + y * iDstH) * 2 + 1] = Math.max(Math.min(t.y, iDstH - 1), 0);
aMapT[(x + y * iDstH)] = t.t;
}
}
// Обновляем сцену
updateScene();
};
function updateScene() {
// Обновляем последние координаты
iLastX = iLastX + iXSpeed;
iLastY = iLastY + iYSpeed;
// Реверс скорости
if (iLastX + 1 > ctx.canvas.width - iDstW/2) {
iXSpeed = -4;
}
if (iLastX - 1 < iDstW/2) {
iXSpeed = 4;
}
if (iLastY + 1 > ctx.canvas.height - iDstH/2) {
iYSpeed = -3;
}
if (iLastY - 1 < iDstH/2) {
iYSpeed = 3;
}
// Сдвигаем второй объект
canvasObj.style.left = iLastX - Math.floor(iDstW / 2) + 'px';
canvasObj.style.top = iLastY - (Math.floor(iDstH / 2)) + 'px';
// Выводим искажающую сферу
var aData = ctx.getImageData(iLastX - Math.ceil(iDstW / 2), iLastY - Math.ceil(iDstH / 2), iDstW, iDstH + 1);
for (var j = 0; j < iDstH; j++) {
for (var i = 0; i < iDstW; i++) {
var u = aMap[(i + j * iDstH) * 2];
var v = aMap[(i + j * iDstH) * 2 + 1];
var t = aMapT[(i + j * iDstH)];
var x = Math.floor(u);
var y = Math.floor(v);
var kx = u - x;
var ky = v - y;
for (var c = 0; c < 3; c++) {
aBitmap.data[(i + j * iDstH) * 4 + c] =
(aData.data[(x + y * iDstH) * 4 + c] * (1 - kx) + aData.data[((x + 1) + y * iDstH) * 4 + c] * kx) * (1-ky) * t +
(aData.data[(x + (y + 1) * iDstH) * 4 + c] * (1 - kx) + aData.data[((x + 1) + (y + 1) * iDstH) * 4 + c] * kx) * (ky) * t;
}
}
}
ctxObj.putImageData(aBitmap,0,0);
// Обновляем таймер
setTimeout(updateScene, 16);
}
};