在做一次活動頁面的時候,設計師提出了這么一個想法:希望將一個圖片打碎之后,在生成一個手機殼的效果。
最初的想發就是查看一下網上是否有一些成熟的方案可以學習和借鑒,於是找到了一個
framentfly.js,感覺整體效果是有那么一點,和自己想的效果還是有差距的。再就是找到奇舞團的博客上有這么一片文章,
“文字”聚合、散出動畫,相對來說這個就比較接近我想要的效果了:有入場動畫、有出場動畫,思路也比較清晰。
通過上述兩種實現,想到大概使用dom或者canvas可以對背景圖片進行相應的處理。
於是乎整理出來一個小插件,完成圖片的碎片效果入場和出場。支持簡單的切片個數和動畫效果的配置。
首先看一下
效果。
接收的參數配置:
//默認配置數據
var defaultOpts = {
backgroundImage: '',
rows: 10, //划分的行數,建議小於30
cols: 10, //划分的列數,建議小於30
//顯示相關配置
show: {
perspective: 800, //視角距離,單位px
rotateX: [-180, 180], //X軸翻轉角度范圍
rotateY: [-180, 180], //Y軸翻轉角度范圍
scale: 2, //放大倍數
translateZ: 600 //Z軸移動距離
},
//隱藏相關配置
hide: {
perspective: 800, //視角距離,單位px
rotateX: [-180, 180], //X軸翻轉角度范圍
rotateY: [-180, 180], //Y軸翻轉角度范圍
scale: 2, //放大倍數
translateZ: 600 //Z軸移動距離
}
};
使用:對外提供兩種方法和一個動畫狀態。實例化之后可以顯示或者隱藏,並且可以監聽動畫的執行狀態。
具體的實現方式:
/**
* 圖片碎片效果
* author: hxl
* modified: blackMao
*/
(function( root , factory ){
if( typeof define === 'function' && define.amd ){
//AMD
define(factory);
}else if( typeof exports === 'object' ){
//Node , CommonJS之類
module.exports = factory();
}else{
//直接暴露全局變量
root.Fragment = factory();
}
})( window , function(){
'use strict';
//默認配置數據
var defaultOpts = {
backgroundImage: '',
rows: 10, //划分的行數,建議小於30
cols: 10, //划分的列數,建議小於30
//顯示相關配置
show: {
perspective: 800, //視角距離,單位px
rotateX: [-180, 180], //X軸翻轉角度范圍
rotateY: [-180, 180], //Y軸翻轉角度范圍
scale: 2, //放大倍數
translateZ: 600 //Z軸移動距離
},
//隱藏相關配置
hide: {
perspective: 800, //視角距離,單位px
rotateX: [-180, 180], //X軸翻轉角度范圍
rotateY: [-180, 180], //Y軸翻轉角度范圍
scale: 2, //放大倍數
translateZ: 600 //Z軸移動距離
}
};
var divCX, divCY, showNodes, hideNodes;
/**
* 分片構造函數
* @param {Object} element 元素
* @param {Object} opts 配置參數
*/
function Fragment(element, opts) {
//初始化數據
this.element = element || document.body;
this.opts = _mix(defaultOpts, opts, true);
this.hash = _getHash();
this._init();
}
Fragment.constructor = Fragment;
//是否正在播放動畫
Fragment.prototype.isAnimating = false;
/**
* 隱藏圖片
* @param {Fucntion} cb 回調函數
*/
Fragment.prototype.hide = function(cb) {
var self = this;
var hideOpts = this.opts.hide;
var R = this.opts.rows;
var C = this.opts.cols;
var perspective = hideOpts.perspective;
var rotateX = hideOpts.rotateX;
var rotateY = hideOpts.rotateY;
var translateZ = hideOpts.translateZ;
var scale = hideOpts.scale;
if(this.isAnimating) return;
hideNodes = 0;
this.isAnimating = true;
for(var i = 0; i < R; i++) {
for(var j = 0; j < C; j++) {
var oNewDiv = document.getElementById('new_'+self.hash+i+'_'+j);
if(!oNewDiv) {
cb('no element');
return;
}
//飛走——跟中心的距離——方向
var l=oNewDiv.offsetLeft+oNewDiv.offsetWidth/2;
var t=oNewDiv.offsetTop+oNewDiv.offsetHeight/2;
var disX=l-divCX;
var disY=t-divCY;
(function (oNewDiv, disX, disY){
setTimeout(function (){
var style = 'perspective('+perspective+'px) translate3d('+disX+'px, '+disY+'px, '+translateZ+'px) rotateY('+_random(rotateX)+'deg) rotateX('+_random(rotateY)+'deg) scale('+scale+')';
_addStylesPrefix(oNewDiv, 'transform', style);
oNewDiv.style.opacity=0;
setTimeout(function (){
self.element.removeChild(oNewDiv);
hideNodes ++;
if(hideNodes == i * j) {
self.isAnimating = false;
cb && cb();
};
}, 600);
}, _random(1, 301));
})(oNewDiv, disX, disY);
}
}
};
/**
* 顯示圖片
* @param {Fucntion} cb 回調函數
*/
Fragment.prototype.show = function(cb) {
var self = this;
var showOpts = this.opts.show;
var R = this.opts.rows;
var C = this.opts.cols;
var backgroundImage = this.opts.backgroundImage || 'http://p3.so.qhimg.com/t01b445a5fff711ae43.jpg';
var perspective = showOpts.perspective;
var rotateX = showOpts.rotateX;
var rotateY = showOpts.rotateY;
var translateZ = showOpts.translateZ;
var scale = showOpts.scale;
if(this.isAnimating) return;
showNodes = 0;
this.isAnimating = true;
for(var i = 0; i < R; i++) {
for(var j = 0; j < C; j++) {
//創建
var w=Math.floor(self.element.offsetWidth/C);
var h=Math.floor(self.element.offsetHeight/R);
var oNewDiv=document.createElement('div');
oNewDiv.id='new_'+self.hash+i+'_'+j;
oNewDiv.style.opacity=0;
oNewDiv.style.left=j*w+'px';
oNewDiv.style.top=i*h+'px';
oNewDiv.style.width=w+'px';
oNewDiv.style.height=h+'px';
oNewDiv.style.background = 'url('+backgroundImage+') no-repeat';
oNewDiv.style.position = 'absolute';
_addStylesPrefix(oNewDiv, 'transition', '0.6s all ease');
_addStylesPrefix(oNewDiv, 'transform', 'translate(0,0) translateZ(0) scale(1,1) rotateX(0deg) rotateY(0deg)');
var offsetLeft = j*w;
var offsetTop = i*h;
var l=offsetLeft+w/2;
var t=offsetTop+h/2;
var disX=l-divCX;
var disY=t-divCY;
var style = 'perspective('+perspective+'px) translate3d('+disX+'px, '+disY+'px, '+translateZ+'px) rotateY('+_random(rotateX)+'deg) rotateX('+_random(rotateY)+'deg) scale('+scale+')';
_addStylesPrefix(oNewDiv, 'transform', style);
oNewDiv.style.opacity=0;
oNewDiv.style.backgroundPosition = '-'+offsetLeft+'px -'+offsetTop+'px';
self.element.appendChild(oNewDiv);
//飛來——跟中心的距離——方向
(function (oNewDiv, disX, disY){
setTimeout(function (){
_addStylesPrefix(oNewDiv, 'transform', 'translate3d(0,0,0)');
oNewDiv.style.opacity=1;
showNodes ++;
if(showNodes == i * j) {
self.isAnimating = false;
cb && cb();
};
}, _random(300, 500));
})(oNewDiv, disX, disY);
}
}
};
Fragment.prototype._init = function() {
//初始化數據
divCX = this.element.offsetWidth/2; //容器的中心點
divCY = this.element.offsetHeight/2; //容器的中心點
showNodes = 0; //顯示節點數
hideNodes = 0; //消失節點數
//初始化dom樣式
_addStylesPrefix(this.element, 'transform-style', 'preserve-3d');
};
/**
* 是否為對象
* @param obj
* return {Boolean}
*/
function _isObject(obj) {
return Object.prototype.toString.call(obj) == '[object Object]';
}
/**
* 是否為數組
* @param arr
* return {Boolean}
*/
function _isArray(arr) {
return Object.prototype.toString.call(arr) == '[object Array]';
}
/**
* mix方法
* @param {Object} target 目標對象
* @param {Object} source 源對象
* @param {Boolean} [override] 是否覆蓋
* return {Object}
*/
function _mix(target, source, override) {
override = override == true ? true : false;
for (var p in source) {
if (source.hasOwnProperty(p)) {
if(override && _isObject(target[p]) && _isObject(source[p])) {
_mix(target[p], source[p]);
}else {
target[p] = source[p];
}
}
}
return target;
}
/**
* 隨機函數
* @param {Number} n
* @param {Number} m
* @param {Number}
*/
function _random(n, m) {
var min = 0;
var max = 0;
if(_isArray(n)) {
m = n[1];
n = n[0];
}
min = Math.min(m, n);
max = Math.max(m, n);
return parseInt(Math.random()*Math.abs(max-min)+min);
}
/**
* 加前綴
* @param {Object} 操作元素
* @param {String} styleName 名稱
* @param {String} style 樣式字符串
*/
function _addStylesPrefix(element, styleName, style) {
var name = '';
styleName = styleName.split('-');
for(var i = 0; i < styleName.length; i++) {
name += styleName[i].replace(/(\w)/,function(v){
return v.toUpperCase()
});
}
element.style['Moz'+name] = style;
element.style['webkit'+name] = style;
element.style['O'+name] = style;
element.style['ms'+name] = style;
if(styleName.length == 1) {
name = name.toLowerCase();
}
element.style[name] = style;
}
/**
* 獲取Hash值
*/
function _getHash() {
var hash = '';
var hashMap = ['B', 'l', 'a', 'c', 'k', 'M', 'a', 'o', '=', '8'];
for(var i = 0; i < hashMap.length; i++) {
var index = Math.floor(Math.random() * 10);
var isCap = Math.random() > 0.5 ? true : false;
if(isCap) {
hash += hashMap[index].toUpperCase();
}else {
hash += hashMap[index].toLowerCase();
}
}
return hash;
}
return Fragment;
});
在這里,再次感謝上述兩位作者提供的思路,特別是奇舞團的hxl。
