前幾天那誰發(fā)布的《月熊志》那個網(wǎng)站,我對里面的視頻拼圖這個效果很感興趣。琢磨好幾天,終于搞定了,并且知道是怎么做出來的了,在這里分享給大家。
大家先去這個網(wǎng)站看效果(網(wǎng)址:http://moonbear.animalsasia.org/ie/),然后往下看具體怎么實現(xiàn),我會在這里很詳細(xì)的給大家講解。
網(wǎng)站中第一部分第二頁《月熊的標(biāo)志》是月熊志中互動性較強(qiáng)的一頁,頁面上會隨機(jī)分布9塊視頻碎片,用戶可以通過鼠標(biāo)或者觸控移動碎片完成拼圖。
在這個Demo中,我們需要引用2個JavaScript庫,jQuery和Hammer.js。
Hammer.js 是一個手勢觸控JS庫,能夠為網(wǎng)頁加入Tap、Swipe、Drag等事件,并且同時支持鼠標(biāo)和觸控輸入,免去自己監(jiān)聽事件和判斷瀏覽器兼容等問題。
首先,我們在頁面中建立一個九宮格:
HTML:
<div id="puzzle">
<div class="container"><i></i></div>
…
<div class="container"><i></i></div>
</div>
CSS:
#puzzle
position: absolute;
top: 50%;
left: 38%;
margin-top: -190px;
width: 678px;
height: 381px;
#puzzle .container
float: left;
width: 226px;
height: 127px;
#puzzle .container i
display: block;
margin: 4px;
width: 218px;
height: 119px;
background: #fff;
每個宮格(.container)的大小是226*127,其中白色部分(.container i)是218*119。
接著我們在頁面中插入視頻,我們使用HTML5 中新增的Video標(biāo)簽,并且為了兼容多數(shù)瀏覽器,使用了2種格式的視頻源,然后設(shè)置視頻為自動播放(Autoplay)和循環(huán)播放(Loop),視頻源的大小建議和九宮格保持一致:
HTML:
<video width="678" height="381" id="video" autoplay loop>
<source src="../video/findjasper.mp4" type="video/mp4">
<source src="../video/findjasper.ogv" type="video/ogg; codecs='theora, vorbis'">
</video>
最后把視頻隱藏起來,在幕后默默的運(yùn)行即可:
CSS:
#video
display: none;
視頻碎片本身是一個個canvas元素,通過JS將Video的幀畫面分塊循環(huán)繪制到canvas上。
JS:
//為數(shù)組添加隨機(jī)打亂方法
Array.prototype.shuffle = function ()
var l = this.length,
i = l;
while (i--)
var p = parseInt(Math.random() * l),
t = this[i];
this[i] = this[p];
this[p] = t;
;
return this;
;
//隨機(jī)函數(shù),隨機(jī)返回min~max中的任一數(shù)值
function random(min, max)
return parseInt(Math.random() * (max - min + 1) + min);
;
上面的JS方法/函數(shù)接下來會用到。
JS:
var PIECE_WIDTH = 226,
PIECE_HEIGHT = 127,
$body = $("body"),
video = $("#video")[0],
$puzzle = $("#puzzle"),
$puzzleItems = $puzzle.find(".container"),
zIndex = 2,
ctxs = [],
rndArray = [0, 1, 2, 3, 4, 5, 6, 7, 8].shuffle();
以上是會用到的變量,其中需要特別說明的是zIndex用來保存碎片的z-index值,ctxs用來保存碎片的canvas上下文,rndArray是一個0~8的隨機(jī)數(shù)組。
JS:
//循環(huán)創(chuàng)建碎片
for (var i = 0; i < 9; i++)
var index = rndArray[i], //分配隨機(jī)位置
piece = document.createElement("canvas"); //創(chuàng)建canvas元素
piece.className = "piece";
piece.width = PIECE_WIDTH;
piece.height = PIECE_HEIGHT;
ctxs.push(piece.getContext("2d")); //把上下文push到ctxs數(shù)組,方便繪制時調(diào)用
//使用random函數(shù)給碎片設(shè)置一個隨機(jī)的位置
//使用css3 transform旋轉(zhuǎn)碎片角度,使拼圖更加真實
//最后把碎片所對應(yīng)的宮格(.container)保存到data("container")中
$(piece).css(
left: random(50, window.innerWidth - PIECE_WIDTH),
top: random(50, window.innerHeight - PIECE_HEIGHT),
transform: "rotate(" + random(-25, 25) + "deg)"
).data("container", $puzzleItems.eq(index)).appendTo($body);
;
目前并沒有限制碎片出現(xiàn)的位置,所以碎片可能會遮蓋頁面上的文字,你可以自己加以完善。
如果現(xiàn)在運(yùn)行頁面,你就能看到頁面上出現(xiàn)的隨機(jī)碎片了,但是由于還沒有把視頻繪制到頁面上,所以只能看到黑色。
繪制視頻到canvas其實十分簡單,主要用到的是canvas的drawImage方法。
drawImage方法接收9個參數(shù)。
context.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight)
image指向要使用的來源,可以是圖片、視頻或者Canvas元素,
sourceX和sourceY指image上繪制的左上角坐標(biāo),
sourceWidth 和 sourceHeight指從image上要繪制的寬和高,
destX和destY表示將image繪制到畫布上的左上角坐標(biāo),
destWidth 和destHeight表示繪制到畫布上的寬和高。
看文字的話可能比較難懂,看下面這張圖應(yīng)該能幫助你理解:
我們創(chuàng)建一個drawVideo函數(shù)繪制視頻
JS:
function drawVideo()
for (var i = 0; i < 9; i++)
var index = rndArray[i], //當(dāng)前碎片位置
row = Math.floor(index / 3), //因為宮格是3x3的,所以用取余數(shù)獲取行數(shù)
col = Math.floor(index % 3); //與3取模獲得列數(shù)
ctxs[i].drawImage(video, (col * PIECE_WIDTH), (row * PIECE_HEIGHT), PIECE_WIDTH, PIECE_HEIGHT, 0, 0, PIECE_WIDTH, PIECE_HEIGHT); //row和col分別乘以宮格的寬高就是要從視頻上繪制的左上角坐標(biāo)
;
;
setInterval(drawVideo, 50);
然后調(diào)用setInterval每隔50毫秒循環(huán)繪制。
hammer.js的事件對象添加一個gesture對象,里面保存了關(guān)于此次操作的相關(guān)信息,比如移動距離、移動速率、移動角度、持續(xù)時間等。
我們先創(chuàng)建3個函數(shù),對應(yīng)拖拽過程中的3個事件。
開始拖拽碎片時,加上.dragging(添加box-shadow),并設(shè)置更高一層的z-index保證在所有碎片之上,最后保存當(dāng)前的位置到data("offset")中。
function dragStart()
var $piece = $(this);
$piece.addClass("dragging").css("z-index", zIndex++).data("offset", $piece.offset());
;
在拖拽碎片時,只需要給之前保存的offset加上移動距離(deltaX/ deltaY),就是現(xiàn)在的正確位置。
function drag(event)
var $piece = $(this),
pieceOffset = $piece.data("offset");
$piece.css(
left: pieceOffset.left + event.gesture.deltaX,
top: pieceOffset.top + event.gesture.deltaY
);
;
最后拖拽結(jié)束,移除dragging類。
如果碎片的中心點(diǎn)在對應(yīng)的宮格內(nèi)部,就移動的宮格內(nèi),并關(guān)閉hammer。
如果九宮格內(nèi)有9塊碎片,就完成拼圖了!
function dragEnd(event)
var $piece = $(this),
pieceOffset = $piece.data("offset");
$piece.removeClass("dragging");
var centerX = pieceOffset.left + event.gesture.deltaX + PIECE_WIDTH / 2,
centerY = pieceOffset.top + event.gesture.deltaY + PIECE_HEIGHT / 2,
$container = $piece.data("container"),
containerOffset = $container.offset();
if (centerX > containerOffset.left && (centerX < containerOffset.left + PIECE_WIDTH) && centerY > containerOffset.top && centerY < (containerOffset.top + PIECE_HEIGHT))
$container.prepend($piece.removeAttr("style").data("hammer").off());
if ($puzzle.find(".piece").length == 9)
// bingo.
;
;
;
$(".piece").hammer(
prevent_default: true
).on("dragstart", dragStart).on("drag", drag).on("dragend", dragEnd);
最后初始化碎片并綁定函數(shù)到對應(yīng)的事件上,這個互動小游戲就完成了。
在眾多HTML5網(wǎng)站里,這個網(wǎng)站運(yùn)用的特效算是比較多了,尤其是交互體驗,非常值得我們搞開發(fā)的研究學(xué)習(xí)。
其實,網(wǎng)站上還有很多其他互動方面的體驗效果,做的都挺不錯。大家可以自己前去體驗:http://moonbear.animalsasia.org/ie/。希望本文能給大家?guī)硪恍╈`感,運(yùn)用到自己的網(wǎng)站開發(fā)上面去。