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小時內與您取得聯系。