整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

惊艳的动画引擎,简单、轻盈、开源-Animejs

惊艳的动画引擎,简单、轻盈、开源-Animejs

Anime.js是一个轻量级的JavaScript动画库,具有简单但功能强大的API。它与CSS属性,SVG,DOM属性和JavaScript对象一起使用。






Github

在Github上已收获近35k的star数,可见其非常受广大使用者的热爱!

https://github.com/juliangarnier/anime/

特性

  • 复杂的交错动画变得简单


  • css分层转换

在单个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
});

Demo

  • 文字动画

<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)定义模板字符串

定义字符模板有两种方式,

  • 方式一:在 js 代码中定义
  • var template=['<ul>',
  • '<li>',
  • '</li>',
  • '</ul>
  • ].join('');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 方式二:直接把模板内容用 script 定义在 HTML 中
  • <script id="tpl" type="text/html">
  • {{name}}
  • </script>
  • 1
  • 2
  • 3
  • 4
  • 然后在编译模板之前,通过获取 tpl 的 innerHTML 定义原始模板串:
  • var template=document.getElementById('tpl')
  • .innerHTML.trim();
  • 1
  • 2
  • 3

(2)预编译模板

使用 parse 函数进行预编译模板,即

Mustache.parse(template);

  • 1
  • 2

要注意,经过预编译后的 template 已经不是原来的模板字符串了,连接数据类型都变成了数组类型。

(3)渲染模板

使用 render 函数进行渲染,即

var rendered=Mustache.render(template, obj)

  • 1
  • 2

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}}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

循环使用:

{

"stooges":[

{"name":"MOE"},

{"name":"LARRY"}

]

}

{{#stooges}}

{{name}}

{{/stooges}}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

(2)tpl/helloworld.mustache

<div>

{{value}}

</div>

  • 1
  • 2
  • 3
  • 4

(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;

})

});

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(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.

  • 1
  • 2
  • 3
  • 4

错误消息为:

跨域请求仅支持: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,否则不加载。

使用原生js实现图片的延时加载

延时加载需要传入的参数:

var selector=options.selector || 'img',
 imgSrc=options.src || 'data-src',
 defaultSrc=options.defaultSrc || '',
 wrapper=options.wrap || body;

其中:

  • wrapper :延时加载的容器。在该容器下,所有符合图片选择器条件的图片均会延时加载。
  • selector :图片选择器。表示需要延迟加载的图片的选择器,如 img.lazyload-image ,默认为所有的 img 标签。
  • imgSrc :图片真实地址存放属性。表示图片的真实路径存放在标签的哪个属性中,默认为 data-src。
  • defaultSrc :初始加载的图片地址,默认为空,当为空时,不处理延时加载的图片的路径,若图片本身没有路径,则显示为空。
  • 获取容器中所有的图片。
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;
 };
}

希望本文能帮助到您!

点赞+转发,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)

关注 {我},享受文章首发体验!

每周重点攻克一个前端技术难点。更多精彩前端内容私信 我 回复“教程”