在做一次活动页面的时候,设计师提出了这么一个想法:希望将一个图片打碎之后,在生成一个手机壳的效果。
最初的想发就是查看一下网上是否有一些成熟的方案可以学习和借鉴,于是找到了一个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。