Anime.js是一个轻量级的JavaScript动画库,具有简单但功能强大的API。它与CSS属性,SVG,DOM属性和JavaScript对象一起使用。
在Github上已收获近35k的star数,可见其非常受广大使用者的热爱!
https://github.com/juliangarnier/anime/
在单个HTML元素上同时以不同的时间对多个CSS变换属性进行动画处理。
时间就是一切
使用完整的内置回调和控件功能同步播放,暂停,控制,倒退和触发事件。
HTML,JS,CSS,SVG
npm install animejs --save
ES6:
import anime from 'animejs/lib/anime.es.js';
CommonJS:
const anime=require('animejs');
<script src="anime.min.js"></script>
anime({
targets: 'div',
translateX: 250,
rotate: '1turn',
backgroundColor: '#FFF',
duration: 800
});
<h1 class="ml1">
<span class="text-wrapper">
<span class="line line1"></span>
<span class="letters">THURSDAY</span>
<span class="line line2"></span>
</span>
</h1>
.ml1 {
font-weight: 900;
font-size: 3.5em;
}
.ml1 .letter {
display: inline-block;
line-height: 1em;
}
.ml1 .text-wrapper {
position: relative;
display: inline-block;
padding-top: 0.1em;
padding-right: 0.05em;
padding-bottom: 0.15em;
}
.ml1 .line {
opacity: 0;
position: absolute;
left: 0;
height: 3px;
width: 100%;
background-color: #fff;
transform-origin: 0 0;
}
.ml1 .line1 { top: 0; }
.ml1 .line2 { bottom: 0; }
var textWrapper=document.querySelector('.ml1 .letters');
textWrapper.innerHTML=textWrapper.textContent.replace(/\S/g, "<span class='letter'>$&</span>");
anime.timeline({loop: true})
.add({
targets: '.ml1 .letter',
scale: [0.3,1],
opacity: [0,1],
translateZ: 0,
easing: "easeOutExpo",
duration: 600,
delay: (el, i)=> 70 * (i+1)
}).add({
targets: '.ml1 .line',
scaleX: [0,1],
opacity: [0.5,1],
easing: "easeOutExpo",
duration: 700,
offset: '-=875',
delay: (el, i, l)=> 80 * (l - i)
}).add({
targets: '.ml1',
opacity: 0,
duration: 1000,
easing: "easeOutExpo",
delay: 1000
});
anime是一个非常值得使用的动画引擎,它足够简单,足够满足需求,足够的轻量,足够的惊艳!enjoy it!
、Mustache简介
mustache.js 是一个简单强大的 JavaScript 模板引擎。使用mustache前需要通过script标签引入它的js文件,然后按以下步骤操作:
(1)定义模板字符串
定义字符模板有两种方式,
(2)预编译模板
使用 parse 函数进行预编译模板,即
Mustache.parse(template);
要注意,经过预编译后的 template 已经不是原来的模板字符串了,连接数据类型都变成了数组类型。
(3)渲染模板
使用 render 函数进行渲染,即
var rendered=Mustache.render(template, obj)
obj 是数据源对象,mustache 会把模板中属性标签替换成与 obj 对象属性名相同的属性值。
(4)替换 DOM 内容
将渲染后的数据挂载到DOM树上。
2、mustache 标签
(1)变量:{{prop}}
该标签可将数据源对象上 prop 属性对应的值,转换成字符串进行输出,该标签渲染逻辑为:
1)如果 prop 引用的值是 null 或 undefined,则渲染成空串;
2)如果 prop 引用的是一个函数,则在渲染是自动执行这个函数,并把这个函数返回值作为渲染结果,假如这个返回值是 null 或 undefined,那么渲染结果仍然为空串,否则把返回值转成字符串作为渲染结果;
3)其他场景,直接把 prop 引用的值转为字符串作为渲染结果;
4)默认情况下,在渲染该标签时,是对 prop 的原始值进行 URL 编码或者 HTML 编码之后再输出,若要阻止这种编码行为,应该使用 {{{prop}}}
(2)带有 HTML 的变量:{{{arg}}}
(3){{#variable}} … {{/variable}}
该标签可以同时完成 if-else 和 for-each 以及动态渲染的模板功能。在这对标签之间,可以定义其他模板内容,嵌套说有标签。
1)if-else
只有 variable 属性在数据源对象上存在,并且不为 falsy 值(JavaScript 6 个 falsy 值:null,undefined,NaN,0,false,空字符串),并且不为空数组的情况下,标签之间的内容才会被渲染,否则不会被渲染。
注意:当 variable 属性引用的是一个函数的时候,{{#variable}}会自动调用这个函数,并把函数的返回值作为 if-else 渲染逻辑的判断依据,也就是说如果这个函数的返回值是 falsy 值或者是空数组的时候,那么这对标签之间的内容还是不会显示。
2)for-each
当 variable 属性所引用的是一个非空数组时,这对标签之间的内容将会根据数组大小进行迭代,并且当数组元素为对象时,还会把该对象作为每一次迭代的上下文,以便迭代时标签可以直接引用数组元素上的属性。
数组循环时 . 作为下标,或者使用对象属性名提取属性值。
3)动态渲染
当 variable 属性所引用的是一个函数,并且这个函数的返回值还是一个函数的话,mustache 会再次调用这个返回的函数,并给它传递 2 个参数: text 表示原来的模板内容, render 表示 mustache 内部的执行渲染的对象,以便在这个函数内部可以通过这个 render对象,结合原来的模板内容,自定义渲染逻辑,并把函数的返回值作为渲染结果。
(4){{^variable}}…{{/variable}}
这对标签,与{{#variable}} … {{/variable}}的 if-else 渲染执行相反逻辑,即只有在 variable 属性不存在或者引用的是一个 falsy 值,或者是一个空数组时才会显示标签之间的内容,否则不会显示。
(5)注解:{{!注解}}
(6)举例:对象
{
"name":{
"first":"Mi",
"last":"Jack"
}
"age": "20"
}
{{name.first}}{{name.last}}
{{age}}
循环使用:
{
"stooges":[
{"name":"MOE"},
{"name":"LARRY"}
]
}
{{#stooges}}
{{name}}
{{/stooges}}
3、示例
(1)index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div id="J_wrapper"></div>
<script src="require.js" charset="UTF-8"></script>
<script>require(['app/tpl']);</script>
</body>
</html>
(2)tpl/helloworld.mustache
<div>
{{value}}
</div>
(3)app/tpl.js
define(["mustache"],function(mustache){
require(['text!tpl/helloworld.mustache'],function(tpl){
mustache.parse(tpl)
var view={value : "hello world"},
html=mustache.render(tpl, view); // 第二个参数必须是对象
document.getElementById("J_wrapper").innerHTML=html;
})
});
(4)引入的 js 有: mustache.js、require.js、text.js
参考:http://www.cnblogs.com/lyzg/p/5133250.html
4、注意
当我们双击HTML文件,或者在Sublime中右键选择“open in browser”,浏览器会报一个错:
Failed to load file:///E:/testspace/mustache_test/tpl/helloworld.mustache:
Cross origin requests are only supported for protocol schemes:
http, data, chrome, chrome-extension, https.
错误消息为:
跨域请求仅支持:http, data, chrome, chrome-extension, https 等,不支持 file协议。这是由于浏览器(Webkit内核)的安全策略决定了file协议访问的应用无法使用 XMLHttpRequest对象。
sublime 默认是没有内置HTTP服务器的,所以是以 file 的方式打开,并产生了该问题。
(1)配置浏览器
Windows:
设置Chrome的快捷方式属性,在“目标”后面加上--allow-file-access-from-files,注意前面有个空格,重新打开Chrome即可。
Mac:
只能通过终端打开浏览器:打开终端,输入下面命令:open -a “Google Chrome” –args –disable-web-security然后就可以屏蔽安全访问了[ –args:此参数可有可无]
(2)安装HTTP服务器
若使用IDE,则无需配置,因为每个用于Web开发的IDE都内置HTTP服务器。
编辑器一般没有内置HTTP服务器,下面以sublime为例进行安装 Sublime Server插件。
1、在package control install中搜索 sublime server,然后安装。(具体安装不做略,与其他插件安装步骤一样)
2、启动sublime server。方法:Tool → SublimeServer → Start SublimeServer
3、打开HTML文件,在右键菜单中选择View in SublimeServer,此时就可以以HTTP方式在浏览器中访问该文件了。
若View in SublimeServer 为灰色不可点时,可能是未启动成功,或者端口已被占用(SublimeServer默认使用8080端口
用原生的js实现简易的图片延时加载。
图片延迟加载也称 “懒加载”,通常应用于图片比较多的网页
假如一个网页中,含有大量的图片,当用户访问网页时,那么浏览器会发送n个图片的请求,加载速度会变得缓慢,性能也会下降。如果使用了延时加载,当用户访问页面的时候,只加载首屏中的图片;后续的图片只有在用户滚动时,即将要呈现给用户浏览时再按需加载,这样可以提高页面的加载速度,也提升了用户体验。而且,统一时间内更少的请求也减轻了服务器中的负担。
基本原理就是最开始时,所有图片都先放一张占位图片(如灰色背景图),真实的图片地址则放在 data-src 中,这么一来,网页在打开时只会加载一张图片。
然后,再给 window 或 body 或者是图片主体内容绑定一个滚动监听事件,当图片出现在可视区域内,即滚动距离 + 窗体可视距离 > 图片到容器顶部的距离时,将讲真实图片地址赋值给图片的 src,否则不加载。
延时加载需要传入的参数:
var selector=options.selector || 'img', imgSrc=options.src || 'data-src', defaultSrc=options.defaultSrc || '', wrapper=options.wrap || body;
其中:
function getAllImages(selector){ return Array.prototype.concat.apply([], wrapper.querySelectorAll(selector)); }
该函数在容器中查找出所有需要延时加载的图片,并将 NodeList 类型的对象转换为允许使用 map 函数的数组。
如果设置了初始图片地址,则加载。
function setDefault(){ images.map(function(img){ img.src=defaultSrc; }) }
给 window 绑定滚动事件
function loadImage(){ var nowHeight=body.scrollTop || doc.documentElement.scrollTop; console.log(nowHeight); if (images.length > 0){ images.map(function(img, index) { if (nowHeight + winHeight > img.offsetTop) { img.src=img.getAttribute(imgSrc); images.splice(index, 1); } }) }else{ window.onscroll=null; } } window.onscroll=loadImage();
每次滚动网页时,都会遍历所有的图片,将图片的位置与当前滚动位置作对比,当符合加载条件时,将图片的真实地址赋值给图片,并将图片从集合中移除;当所有需要延时加载的图片都加载完毕后,将滚动事件取消绑定。
测试是否可行
测试结果:
从chrome的网络请求图中可见,5张图片并不是在网页打开的时候就请求了,而是当滑动到某个区域时才触发加载,基本实现了图片的延时加载。
测试结果
性能调整
上述只是简单的实现了一个延时加载的 demo,还有很多地方需要调整和完善。
调整 1:onscroll 函数可能会被覆盖
问题:
因为有时候页面需要滚动无限加载时,插件会重写 window 的 onscroll 函数,从而导致图片的延时加载滚动监听失效。
解决办法:
需要更改为将监听事件注册到 window 上,移除时只需要移除相应的事件即可。
调整后的代码
function bindListener(element, type, callback){ if (element.addEventListener) { element.addEventListener(type, callback); }else if (element.attachEvent) { //兼容至 IE8 element.attachEvent('on'+type, callback) }else{ element['on'+type]=callback; } } function removeListener(element, type, callback){ if (element.removeEventListener) { element.removeEventListener(type, callback); }else if (element.detachEvent) { element.detachEvent('on'+type, callback) }else{ element['on'+type]=callback; } } function loadImage(){ var nowHeight=body.scrollTop || doc.documentElement.scrollTop; console.log(nowHeight); if (images.length > 0){ images.map(function(img, index) { if (nowHeight + winHeight > img.offsetTop) { img.src=img.getAttribute(imgSrc); images.splice(index, 1); } }) }else{ //解绑滚动事件 removeListener(window, 'scroll', loadImage) } } //绑定滚动事件 bindListener(window, 'scroll', loadImage)
调整2:滚动时的回调函数执行次数太多
问题
在本次测试中,从动图最后可以看到,当滚动网页时,loadImage 函数执行了非常多次,滚轮每向下滚动 100px 基本上就要执行 10 次左右的 loadImage,若处理函数稍微复杂,响应速度跟不上触发频率,则会造成浏览器的卡顿甚至假死,影响用户体验。
解决办法
使用 throttle 控制触发频率,让浏览器有更多的时间间隔去执行相应操作,减少页面抖动。
调整后的代码:
//参考 `underscore` 的源码 var throttle=function(func, wait, options) { var context, args, result; var timeout=null; // 上次执行时间点 var previous=0; if (!options) options={}; // 延迟执行函数 var later=function() { // 若设定了开始边界不执行选项,上次执行时间始终为0 previous=options.leading===false ? 0 : _now(); timeout=null; result=func.apply(context, args); if (!timeout) context=args=null; }; return function() { var now=_now(); // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。 if (!previous && options.leading===false) previous=now; // 延迟执行时间间隔 var remaining=wait - (now - previous); context=this; args=arguments; // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口 // remaining大于时间窗口wait,表示客户端系统时间被调整过 if (remaining <=0 || remaining > wait) { clearTimeout(timeout); timeout=null; previous=now; result=func.apply(context, args); if (!timeout) context=args=null; //如果延迟执行不存在,且没有设定结尾边界不执行选项 } else if (!timeout && options.trailing !==false) { timeout=setTimeout(later, remaining); } return result; }; }; //在调用高频率触发函数处使用 throttle 控制频率在 次/wait var load=throttle(loadImage, 250); //绑定滚动事件 bindListener(window, 'scroll', load); //解绑滚动事件 removeListener(window, 'scroll', load)
调整后的测试
调整后的测试结果
封装为插件形式
;(function(window, undefined){ function _now(){ return new Date().getTime(); } //辅助函数 var throttle=function(func, wait, options) { var context, args, result; var timeout=null; // 上次执行时间点 var previous=0; if (!options) options={}; // 延迟执行函数 var later=function() { // 若设定了开始边界不执行选项,上次执行时间始终为0 previous=options.leading===false ? 0 : _now(); timeout=null; result=func.apply(context, args); if (!timeout) context=args=null; }; return function() { var now=_now(); // 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。 if (!previous && options.leading===false) previous=now; // 延迟执行时间间隔 var remaining=wait - (now - previous); context=this; args=arguments; // 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口 // remaining大于时间窗口wait,表示客户端系统时间被调整过 if (remaining <=0 || remaining > wait) { clearTimeout(timeout); timeout=null; previous=now; result=func.apply(context, args); if (!timeout) context=args=null; //如果延迟执行不存在,且没有设定结尾边界不执行选项 } else if (!timeout && options.trailing !==false) { timeout=setTimeout(later, remaining); } return result; }; }; //分析参数 function extend(custom, src){ var result={}; for(var attr in src){ result[attr]=custom[attr] || src[attr] } return result; } //绑定事件,兼容处理 function bindListener(element, type, callback){ if (element.addEventListener) { element.addEventListener(type, callback); }else if (element.attachEvent) { element.attachEvent('on'+type, callback) }else{ element['on'+type]=callback; } } //解绑事件,兼容处理 function removeListener(element, type, callback){ if (element.removeEventListener) { element.removeEventListener(type, callback); }else if (element.detachEvent) { element.detachEvent('on'+type, callback) }else{ element['on'+type]=null; } } //判断一个元素是否为DOM对象,兼容处理 function isElement(o) { if(o && (typeof HTMLElement==="function" || typeof HTMLElement==="object") && o instanceof HTMLElement){ return true; }else{ return (o && o.nodeType && o.nodeType===1) ? true : false; }; }; var lazyload=function(options){ //辅助变量 var images=[], doc=document, body=document.body, winHeight=screen.availHeight; //参数配置 var opt=extend(options, { wrapper: body, selector: 'img', imgSrc: 'data-src', defaultSrc: '' }); if (!isElement(opt.wrapper)) { console.log('not an HTMLElement'); if(typeof opt.wrapper !='string'){ //若 wrapper 不是DOM对象 或者不是字符串,报错 throw new Error('wrapper should be an HTMLElement or a selector string'); }else{ //选择器 opt.wrapper=doc.querySelector(opt.wrapper) || body; } } //查找所有需要延时加载的图片 function getAllImages(selector){ return Array.prototype.concat.apply([], opt.wrapper.querySelectorAll(selector)); } //设置默认显示图片 function setDefault(){ images.map(function(img){ img.src=opt.defaultSrc; }) } //加载图片 function loadImage(){ var nowHeight=body.scrollTop || doc.documentElement.scrollTop; console.log(nowHeight); if (images.length > 0){ images.map(function(img, index) { if (nowHeight + winHeight > img.offsetTop) { img.src=img.getAttribute(opt.imgSrc); console.log('loaded'); images.splice(index, 1); } }) }else{ removeListener(window, 'scroll', load) } } var load=throttle(loadImage, 250); return (function(){ images=getAllImages(opt.selector); bindListener(window, 'scroll', load); opt.defaultSrc && setDefault() loadImage(); })() }; window.lazyload=lazyload; })(window);
上述代码拷贝到项目中即可使用,使用方式:
//使用默认参数 new lazyload(); //使用自定义参数 new lazyload({ wrapper: '.article-content', selector: '.image', src: 'data-image', defaultSrc: 'example.com/static/images/default.png' });
若在 IE8 中使用,没有 map 函数时,请在引用插件前加入下列处理 map 函数兼容性的代码:
// 实现 ECMA-262, Edition 5, 15.4.4.19 // 参考: http://es5.github.com/#x15.4.4.19 if (!Array.prototype.map) { Array.prototype.map=function(callback, thisArg) { var T, A, k; if (this==null) { throw new TypeError(" this is null or not defined"); } // 1. 将O赋值为调用map方法的数组. var O=Object(this); // 2.将len赋值为数组O的长度. var len=O.length >>> 0; // 3.如果callback不是函数,则抛出TypeError异常. if (Object.prototype.toString.call(callback) !="[object Function]") { throw new TypeError(callback + " is not a function"); } // 4. 如果参数thisArg有值,则将T赋值为thisArg;否则T为undefined. if (thisArg) { T=thisArg; } // 5. 创建新数组A,长度为原数组O长度len A=new Array(len); // 6. 将k赋值为0 k=0; // 7. 当 k < len 时,执行循环. while (k < len) { var kValue, mappedValue; //遍历O,k为原数组索引 if (k in O) { //kValue为索引k对应的值. kValue=O[k]; // 执行callback,this指向T,参数有三个.分别是kValue:值,k:索引,O:原数组. mappedValue=callback.call(T, kValue, k, O); // 返回值添加到新数组A中. A[k]=mappedValue; } // k自增1 k++; } // 8. 返回新数组A return A; }; }
点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注 {我},享受文章首发体验!
每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”
*请认真填写需求信息,我们会在24小时内与您取得联系。