原文来自:git
http://tympanus.net/codrops/2015/05/28/image-tilt-effect/github
所谓的倾斜效果,我也不知如何用语言描述,那就直接看Demo啦,下面咱们会对这个效果的实现原理逐步分析:
http://codepen.io/CodingMonkeyzh/pen/jPYNyrweb
对一个图片添加该效果,首先,咱们须要一个具备宽高的容器。DOM 结构很是简单。app
<div class="container"> <img class="tilt-effect" src="http://placehold.it/500x300" alt="" /> </div>
上面这段结构通过脚本处理以后,会被替换成下面的结构:函数
<div class="container"> <div class="tilt"> <div class="tilt__back" style="background-image: url(http://placehold.it/500x300);"></div> <div class="tilt__front" style="opacity: 0.7; -webkit-transform: perspective(1000px) translate3d(0px, 0px, 0px) rotate3d(1, 1, 1, 0deg); transform: perspective(1000px) translate3d(0px, 0px, 0px) rotate3d(1, 1, 1, 0deg); background-image: url(http://placehold.it/500x300);"></div> <div class="tilt__front" style="opacity: 0.7; -webkit-transform: perspective(1000px) translate3d(0px, 0px, 0px) rotate3d(1, 1, 1, 0deg); transform: perspective(1000px) translate3d(0px, 0px, 0px) rotate3d(1, 1, 1, 0deg); background-image: url(http://placehold.it/500x300);"></div> </div> </div>
咱们利用了filtfx.js这个插件对上面的图片进行处理, 来实现倾斜效果。我在原来的代码中加入了一些注释,来帮助咱们理解。下面咱们对该插件的核心代码进行分析。this
function TiltFx(el, options) { this.el = el; this.options = extend({}, this.options); extend(this.options, options); this._init(); this._initEvents(); }
这是构造函数,若是咱们的文档中,加入了上面的插件,那么插件会遍历文档中具备tilt-effet
的img
元素,来调用构造函数TiltFx()
:url
function init() { // 遍历全部拥有‘title-effect’类的img元素 [].slice.call(document.querySelectorAll('img.tilt-effect')).forEach(function(img) { new TiltFx(img, JSON.parse(img.getAttribute('data-tilt-options'))); }); }
TiltFx()
具备一个原型属性,两个原型方法。原型属性配置了一些默认的参数用于调用:.net
/** * 默认参数 */ TiltFx.prototype.options = { extraImgs: 2, // 额外的辅助图片数量 opacity: 0.7, bgfixed: true, // 底图是否固定 movement: { // 这是一些用于移动的参数 perspective: 1000, translateX: -10, translateY: -10, translateZ: 20, rotateX: 2, rotateY: 2, rotateZ: 0 } }
第一个原型方法是_init()
,用于初始化DOM结点,生成咱们的目标DOM结点:prototype
TiltFx.prototype._init = function() { this.tiltWrapper = document.createElement('div'); this.tiltWrapper.className = 'tilt'; // main image element. this.tiltImgBack = document.createElement('div'); this.tiltImgBack.className = 'tilt__back'; this.tiltImgBack.style.backgroundImage = 'url(' + this.el.src + ')'; this.tiltWrapper.appendChild(this.tiltImgBack); // image elements limit. if (this.options.extraImgs < 1) { this.options.extraImgs = 1; } else if (this.options.extraImgs > 5) { this.options.extraImgs = 5; } if (!this.options.movement.perspective) { this.options.movement.perspective = 0; } // add the extra image elements. this.imgElems = []; for (var i = 0; i < this.options.extraImgs; ++i) { var el = document.createElement('div'); el.className = 'tilt__front'; el.style.backgroundImage = 'url(' + this.el.src + ')'; el.style.opacity = this.options.opacity; this.tiltWrapper.appendChild(el); this.imgElems.push(el); } if (!this.options.bgfixed) { this.imgElems.push(this.tiltImgBack); ++this.options.extraImgs; } // add it to the DOM and remove original img element. this.el.parentNode.insertBefore(this.tiltWrapper, this.el); this.el.parentNode.removeChild(this.el); // tiltWrapper properties: width/height/left/top this.view = { width: this.tiltWrapper.offsetWidth, height: this.tiltWrapper.offsetHeight }; };
另一个原型方式是用于监听鼠标事件之类的:插件
TiltFx.prototype._initEvents = function() { var self = this, moveOpts = self.options.movement; // mousemove event.. this.tiltWrapper.addEventListener('mousemove', function(ev) { requestAnimationFrame(function() { // mouse position relative to the document. var mousepos = getMousePos(ev), // document scrolls. docScrolls = { left: document.body.scrollLeft + document.documentElement.scrollLeft, top: document.body.scrollTop + document.documentElement.scrollTop }, bounds = self.tiltWrapper.getBoundingClientRect(), // mouse position relative to the main element (tiltWrapper). relmousepos = { x: mousepos.x - bounds.left - docScrolls.left, y: mousepos.y - bounds.top - docScrolls.top }; // configure the movement for each image element. for (var i = 0, len = self.imgElems.length; i < len; ++i) { var el = self.imgElems[i], rotX = moveOpts.rotateX ? 2 * ((i + 1) * moveOpts.rotateX / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.rotateX / self.options.extraImgs) : 0, rotY = moveOpts.rotateY ? 2 * ((i + 1) * moveOpts.rotateY / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.rotateY / self.options.extraImgs) : 0, rotZ = moveOpts.rotateZ ? 2 * ((i + 1) * moveOpts.rotateZ / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.rotateZ / self.options.extraImgs) : 0, transX = moveOpts.translateX ? 2 * ((i + 1) * moveOpts.translateX / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.translateX / self.options.extraImgs) : 0, transY = moveOpts.translateY ? 2 * ((i + 1) * moveOpts.translateY / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.translateY / self.options.extraImgs) : 0, transZ = moveOpts.translateZ ? 2 * ((i + 1) * moveOpts.translateZ / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.translateZ / self.options.extraImgs) : 0; el.style.WebkitTransform = 'perspective(' + moveOpts.perspective + 'px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(1,0,0,' + rotX + 'deg) rotate3d(0,1,0,' + rotY + 'deg) rotate3d(0,0,1,' + rotZ + 'deg)'; el.style.transform = 'perspective(' + moveOpts.perspective + 'px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(1,0,0,' + rotX + 'deg) rotate3d(0,1,0,' + rotY + 'deg) rotate3d(0,0,1,' + rotZ + 'deg)'; } }); }); // reset all when mouse leaves the main wrapper. this.tiltWrapper.addEventListener('mouseleave', function(ev) { setTimeout(function() { for (var i = 0, len = self.imgElems.length; i < len; ++i) { var el = self.imgElems[i]; el.style.WebkitTransform = 'perspective(' + moveOpts.perspective + 'px) translate3d(0,0,0) rotate3d(1,1,1,0deg)'; el.style.transform = 'perspective(' + moveOpts.perspective + 'px) translate3d(0,0,0) rotate3d(1,1,1,0deg)'; } }, 60); }); // window resize window.addEventListener('resize', throttle(function(ev) { // recalculate tiltWrapper properties: width/height/left/top self.view = { width: self.tiltWrapper.offsetWidth, height: self.tiltWrapper.offsetHeight }; }, 50)); };
咱们能够看到,监听mousemove
的事件处理函数中的计算比较复杂,关键的部分就是在这里:
var el = self.imgElems[i], rotX = moveOpts.rotateX ? 2 * ((i + 1) * moveOpts.rotateX / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.rotateX / self.options.extraImgs) : 0, rotY = moveOpts.rotateY ? 2 * ((i + 1) * moveOpts.rotateY / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.rotateY / self.options.extraImgs) : 0, rotZ = moveOpts.rotateZ ? 2 * ((i + 1) * moveOpts.rotateZ / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.rotateZ / self.options.extraImgs) : 0, transX = moveOpts.translateX ? 2 * ((i + 1) * moveOpts.translateX / self.options.extraImgs) / self.view.width * relmousepos.x - ((i + 1) * moveOpts.translateX / self.options.extraImgs) : 0, transY = moveOpts.translateY ? 2 * ((i + 1) * moveOpts.translateY / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.translateY / self.options.extraImgs) : 0, transZ = moveOpts.translateZ ? 2 * ((i + 1) * moveOpts.translateZ / self.options.extraImgs) / self.view.height * relmousepos.y - ((i + 1) * moveOpts.translateZ / self.options.extraImgs) : 0; el.style.WebkitTransform = 'perspective(' + moveOpts.perspective + 'px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(1,0,0,' + rotX + 'deg) rotate3d(0,1,0,' + rotY + 'deg) rotate3d(0,0,1,' + rotZ + 'deg)'; el.style.transform = 'perspective(' + moveOpts.perspective + 'px) translate3d(' + transX + 'px,' + transY + 'px,' + transZ + 'px) rotate3d(1,0,0,' + rotX + 'deg) rotate3d(0,1,0,' + rotY + 'deg) rotate3d(0,0,1,' + rotZ + 'deg)';
这里咱们根据鼠标的位置,计算出了各个图层对应的偏移量和旋转角度,而后对它们进行变换便可。
最后mouseleave
以后,咱们再把个个图层恢复到初始位置就好了。