关于 vanilla tilt
vanilla-tilt 是一个 3D 倾斜的 JavaScript 库,具有以下特点
- 流畅
- 轻量级
- 60FPS
- 原生 JS
- 不依赖第三方库
- 简单易用
Ps:详情请参考其官网
举个栗子
本文主要记录对 vanilla-tilt 源码阅读的过程
作用域包裹
老套路,使用 立即执行函数 将自己的业务逻辑包裹起来,这么做的好处:
- 避免全局污染:所有库相关的逻辑,定义的变量,以及使用到的变量,统统封装到该函数作用域中
- 隐私保护:但凡在立即执行函数中声明的变量,函数,除非自己想要暴露,否则从外界是不可能获取的
1 2 3
| var VanillaTilt = (function (){ }())
|
自动加载 & 批量初始化
核心类采用 ES6 的 class
进行编写,使用类的静态方法 static init (elements, settings),对元素进行初始化
自动加载
当加载 vanilla-tilt
库之后,校验是否处在浏览器环境,如果是,则对设置了 data-tilt
属性的元素进行初始化
1 2 3 4 5
| if (document !== undefined) { VanillaTilt.inti(document.querySelectorAll('[data-tilt]')); }
|
批量初始化
在类 VanillaTilt 的静态方法 init 中,优先对元素进行一系列的校验,详情看以下注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static init (elements, settings) { if (elements instanceof Node) { elements = [elements]; } if (elements instanceof NodeList) { elements = [].slice.call(elements); }
if (!(elements instanceof Array)) { return ; }
elements.forEach((element) => { if (!('vanillaTilt' in element)) { element.vanillaTilt = new VanillaTilt(element, settings); } }) }
|
初始化 setting
settings
指定方法
初始化时,以对象的方式传入初始化函数
1 2 3 4 5 6
| <script type="text/javascript"> VanillaTilt.init(document.querySelector(".your-element"), { max: 25, speed: 400 }); </script>
|
以HTML 属性
方式指定有关属性
1
| <div data-tilt data-tilt-reverse="true"></div>
|
实现的核心代码 & 解读
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| extendSettings(settings) { let defaultSettings = { };
let newSettings = {}; for (var property in defaultSettings) { if (property in settings) { newSettings[property] = settings[property]; } else if (this.element.hasAttribute("data-tilt-" + property)) { let attribute = this.element.getAttribute("data-tilt-" + property); try { newSettings[property] = JSON.parse(attribute); } catch (e) { newSettings[property] = attribute; } } else { newSettings[property] = defaultSettings[property]; } }
return newSettings; }
|
自定义真值检测
为了同时兼容以上提到的两种 settings
设置方式,本库自定义了一个真值检测函数
1 2 3 4
| isSettingTrue (data) { return data == '' || data == true || data == 1) }
|
使用 bind
显示指定this
使用如 this.onMouseEnterBind = this.onMouseEnter.bind(this);
的方法,显示的指定 this
三大事件处理函数
onMouseEnter
确定元素位置
1 2 3
|
const rect = this.element.getBoundingClientRect();
|
使用 will-change
进行优化
1
| this.element.style.willChange = "transform";
|
防抖处理
详情见 setTransition ()
方法
onMouseMove
这个库的核心代码
使用 requestAnimationFrame
进行节流处理,防止动画触发太过于频繁
两个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| getValues() { let x = (this.event.clientX - this.left) / this.width; let y = (this.event.clientY - this.top) / this.height;
x = Math.min(Math.max(x, 0), 1); y = Math.min(Math.max(y, 0), 1);
let tiltX = (this.reverse * (this.settings.max / 2 - x * this.settings.max)).toFixed(2); let tiltY = (this.reverse * (y * this.settings.max - this.settings.max / 2)).toFixed(2); let angle = Math.atan2(this.event.clientX - (this.left + this.width / 2), -(this.event.clientY - (this.top + this.height / 2))) * (180 / Math.PI);
return { tiltX: tiltX, tiltY: tiltY, percentageX: x * 100, percentageY: y * 100, angle: angle }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| update() { let values = this.getValues();
this.element.style.transform = "perspective(" + this.settings.perspective + "px) " + "rotateX(" + (this.settings.axis === "x" ? 0 : values.tiltY) + "deg) " + "rotateY(" + (this.settings.axis === "y" ? 0 : values.tiltX) + "deg) " + "scale3d(" + this.settings.scale + ", " + this.settings.scale + ", " + this.settings.scale + ")";
if (this.glare) { this.glareElement.style.transform = `rotate(${values.angle}deg) translate(-50%, -50%)`; this.glareElement.style.opacity = `${values.percentageY * this.settings["max-glare"] / 100}`; }
// 问题:这里为啥会有一个事件 // 对外暴露 this.element.dispatchEvent(new CustomEvent("tiltChange", { "detail": values }));
this.updateCall = null; }
|
onMouseLeave
根据配置,将元素是否恢复到原始状态
Tricks
- 使用 JSON.parse 将获取属性值(字符串)转化为 JavaScript 值
完整注释版 vanilla-tilt.js
写在最后
27 行核心代码写一个简版的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function $ (element) { return document.querySelector(element); }
const tilter = $('#tilt'); const max = 35;
function doTilt (e) { const clientX = e.clientX; const clientY = e.clientY; const rect = this.getBoundingClientRect(); const top = rect.top; const left = rect.left;
const tiltX = max / 2 - (clientX - left) / width * max; const tiltY = (clientY - top) / height * max - max / 2;
this.style.transform = `perspective(1000px) rotateX(${tiltY}deg) rotateY(${tiltX}deg)`; }
function reset () { this.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg)`; }
tilter.addEventListener ('mousemove', doTilt); tilter.addEventListener('mouseleave', reset);
|
效果: