摘要:這篇文章將介紹如何使用原生主要使用語法實(shí)現(xiàn)全屏滾動(dòng)插件,兼容手機(jī)觸屏,觸摸板優(yōu)化,支持自定義頁面動(dòng)畫,壓縮后文件只有。
這篇文章將介紹如何使用原生 JS (主要使用 ES6 語法)實(shí)現(xiàn)全屏滾動(dòng)插件,兼容 IE 10+、手機(jī)觸屏,Mac 觸摸板優(yōu)化,支持自定義頁面動(dòng)畫,壓縮后 gzip 文件只有 2.15KB。完整源碼在這 pure-full-page,點(diǎn)這查看 demo。
1)前面的話現(xiàn)在已經(jīng)有很多全屏滾動(dòng)插件了,比如著名的 fullPage,那為什么還要自己造輪子呢?
現(xiàn)有輪子有以下問題:
首先,最大的問題是最流行的幾個(gè)插件都依賴 jQuery,這意味著在使用 React 或者 Vue 的項(xiàng)目中使用他們是一件十分蛋疼的事:我只需要一個(gè)全屏滾動(dòng)功能,卻還需要把 jQuery 引入,有種殺雞使用宰牛刀的感覺;
其次,現(xiàn)有的很多全屏滾動(dòng)插件功能往往都十分豐富,這在前幾年是優(yōu)勢(shì),但現(xiàn)在(2018-5)可以看作是劣勢(shì):前端開發(fā)已經(jīng)發(fā)生了很大變化,其中很重要的一個(gè)變化是 ES6 原生支持模塊化開發(fā),模塊化開發(fā)最大的特點(diǎn)是一個(gè)模塊最好只專注做好一件事,然后再拼成一個(gè)完整的系統(tǒng),從這個(gè)角度看,大而全的插件有悖模塊化開發(fā)的原則。
對(duì)比之下,通過原生語言造輪子有以下好處:
使用原生語言編寫的插件,自身不會(huì)受依賴的插件的使用場(chǎng)景而影響自身的使用(現(xiàn)在依賴 jQuery 的插件非常不適合開發(fā)單頁面應(yīng)用),所以使用上更加靈活;
搭配模塊化開發(fā),使用原生語言開發(fā)的插件可以只專注一個(gè)功能,所以代碼量可以很少;
最后,隨著 JS/CSS/HTML 的發(fā)展以及瀏覽器不斷迭代更新,現(xiàn)在使用原生語言編寫插件的開發(fā)成本越來越低,那為什么不呢?
2)實(shí)現(xiàn)原理及代碼架構(gòu) 2.1 實(shí)現(xiàn)原理實(shí)現(xiàn)原理見下圖:容器及容器內(nèi)的頁面取當(dāng)前可視區(qū)高度,同時(shí)容器的父級(jí)元素 overflow 屬性值設(shè)為 hidden,通過更改容器 top 值實(shí)現(xiàn)全屏滾動(dòng)效果。
2.2 代碼架構(gòu)代碼編寫的思路是通過 class 定義全屏滾動(dòng)類,使用時(shí)通過 new PureFullPage().init() 使用。
/** * 全屏滾動(dòng)類 */ class PureFullPage { // 構(gòu)造函數(shù) constructor() {} // 原型方法 methods() {} // 初始化函數(shù) init() {} }3)html 結(jié)構(gòu)
鑒于上述實(shí)現(xiàn)原理,對(duì)于 html 的結(jié)構(gòu)有特定要求,如下:頁面容器為 #pureFullPageContainer,所有的頁面為其直接子元素,這里為了方便,直接取 body 為其直接父元素。
4)css 設(shè)置
首先,容器及容器內(nèi)的頁面取當(dāng)前可視區(qū)高度,為每次切換都顯示一個(gè)完整的頁面做準(zhǔn)備;
第二,容器的父級(jí)元素(此處是 body) overflow 屬性值定為 hidden,這樣可以保證每次只會(huì)顯示一個(gè)頁面,其他頁面被隱藏。
經(jīng)過上述設(shè)置,對(duì)容器 top 值,每次更改一個(gè)可視區(qū)高度的距離,便實(shí)現(xiàn)了頁面間的切換,部分代碼如下:
body { /* body 為容器直接的父元素 */ overflow: hidden; } #pureFullPage { /* 只有當(dāng) position 的值不是 static 時(shí),top 值才有效 */ position: relative; /* 設(shè)置初始值 */ top: 0; } .page { /* 此處不能為 100vh,后面詳述 */ /* 其父元素,也就是 #pureFullPage 的高度,通過 js 動(dòng)態(tài)設(shè)置*/ height: 100%; }
Notice:
容器的 position 屬性值需要設(shè)置為 relative,因?yàn)?top 只有在 position 屬性值不為 static 時(shí)才有效;
頁面高度需設(shè)置為當(dāng)前可視區(qū)高度,但不能直接設(shè)置為 100vh,因?yàn)?safari 手機(jī)瀏覽器把地址欄算進(jìn)去計(jì)算 100vh,但地址欄下面的不應(yīng)該算做“可視區(qū)”,畢竟實(shí)際上是“看不見”的區(qū)域。這會(huì)導(dǎo)致 100vh 對(duì)應(yīng)的像素值比 document.documentElement.clientHeight 獲取的像素值大。這樣在切換 top 值時(shí)就不是全屏切換了,實(shí)際上,這種情況下切換的高度小于頁面的高度。
解決 safari 手機(jī)瀏覽器可視區(qū)高度問題:既然通過 js 獲取的 document.documentElement.clientHeight 值是符合預(yù)期的可視區(qū)高度(不包括頂部地址欄和底部工具欄),那就將該值通過 js 設(shè)置為容器的高度,同時(shí),容器內(nèi)的頁面高度設(shè)置為 100%,這樣就可以保證容器及頁面的高度和切換 top 值相同了,也就保證了全屏切換。
// 偽代碼 "#pureFullPage".style.height = document.documentElement.clientHeight + "px";5)監(jiān)控滾動(dòng)/滑動(dòng)事件
這里的滾動(dòng)/滑動(dòng)事件包括鼠標(biāo)滾動(dòng)、觸摸板滑動(dòng)以及手機(jī)屏幕上下滑動(dòng)。
5.1 PC 端PC 端主要解決的問題是獲取鼠標(biāo)滾動(dòng)或觸摸板滑動(dòng)方向,觸摸板上下滑動(dòng)和鼠標(biāo)滾動(dòng)綁定的是同一個(gè)事件:
firefox 是 DOMMouseScroll 事件,對(duì)應(yīng)的滾輪信息(向前滾還是向后滾)存儲(chǔ)在 detail 屬性中,向前滾,這個(gè)屬性值是 3 的倍數(shù),反之,是 -3 的倍數(shù);
firefox 之外的其他瀏覽器是 mousewheel 事件,對(duì)應(yīng)的滾輪信息存儲(chǔ)在 wheelDelta 屬性中,向前滾,這個(gè)屬性值是 -120 的倍數(shù),反之, 120 的倍數(shù)。
macOS 如此,windows 相反?
所以,可以通過 detail 或 wheelDelta 的值判斷鼠標(biāo)的滾動(dòng)方向,進(jìn)而控制頁面是向上還是向下滾動(dòng)。在這里我們只關(guān)心正負(fù),不關(guān)心具體值的大小,為了便于使用,下面基于這兩個(gè)事件封裝了一個(gè)函數(shù):如果鼠標(biāo)往前滾動(dòng),返回負(fù)數(shù),反之,返回正數(shù),代碼如下:
// 鼠標(biāo)滾輪事件 getWheelDelta(event) { if (event.wheelDelta) { return event.wheelDelta; } else { // 兼容火狐 return -event.detail; } },
有了滾動(dòng)事件,就可以據(jù)此編寫頁面向上或者向下滾動(dòng)的回調(diào)函數(shù)了,如下:
// 鼠標(biāo)滾動(dòng)邏輯(全屏滾動(dòng)關(guān)鍵邏輯) scrollMouse(event) { let delta = utils.getWheelDelta(event); // delta < 0,鼠標(biāo)往前滾動(dòng),頁面向下滾動(dòng) if (delta < 0) { this.goDown(); } else { this.goUp(); } }
goDown、goUp 是頁面滾動(dòng)的邏輯代碼,需要特別說明的是必須 判斷滾動(dòng)邊界,保證容器中顯示的始終是頁面內(nèi)容:
上邊界容易確定,為 1 個(gè)頁面(也即可視區(qū))的高度,即如果容器當(dāng)前的上外邊框距離整個(gè)頁面頂部的距離(這里此值正是容器的 offsetTop 值的絕對(duì)值,因?yàn)樗冈氐?offsetTop 值都是 0)大于等于當(dāng)前可視區(qū)高度時(shí),才允許向上滾動(dòng),不然,就證明上面已經(jīng)沒有頁面了,不允許繼續(xù)向上滾動(dòng);
下邊界為 n - 2(n 表示全屏滾動(dòng)的頁面數(shù)) 個(gè)可視區(qū)的高度,當(dāng)容器的 offsetTop 值的絕對(duì)值小于等于 n - 2 個(gè)可視區(qū)的高度時(shí),表示還可以向下滾動(dòng)一個(gè)頁面。
具體代碼如下:
goUp() { // 只有頁面頂部還有頁面時(shí)頁面向上滾動(dòng) if (-this.container.offsetTop >= this.viewHeight) { // 重新指定當(dāng)前頁面距視圖頂部的距離 currentPosition,實(shí)現(xiàn)全屏滾動(dòng), // currentPosition 為負(fù)值,越大表示超出頂部部分越少 this.currentPosition = this.currentPosition + this.viewHeight; this.turnPage(this.currentPosition); } } goDown() { // 只有頁面底部還有頁面時(shí)頁面向下滾動(dòng) if (-this.container.offsetTop <= this.viewHeight * (this.pagesNum - 2)) { // 重新指定當(dāng)前頁面距視圖頂部的距離 currentPosition,實(shí)現(xiàn)全屏滾動(dòng), // currentPosition 為負(fù)值,越小表示超出頂部部分越多 this.currentPosition = this.currentPosition - this.viewHeight; this.turnPage(this.currentPosition); } }
最后添加滾動(dòng)事件:
// 鼠標(biāo)滾輪監(jiān)聽,火狐鼠標(biāo)滾動(dòng)事件不同其他 if (navigator.userAgent.toLowerCase().indexOf("firefox") === -1) { document.addEventListener("mousewheel", scrollMouse); } else { document.addEventListener("DOMMouseScroll", scrollMouse); }5.2 移動(dòng)端
移動(dòng)端需要判斷是向上還是向下滑動(dòng),可以結(jié)合 touchstart(手指開始接觸屏幕時(shí)觸發(fā)) 和 touchend(手指離開屏幕時(shí)觸發(fā)) 兩個(gè)事件實(shí)現(xiàn)判斷:分別獲取兩個(gè)事件開始觸發(fā)時(shí)的 pageY 值,如果觸摸結(jié)束時(shí)的 pageY 大于觸摸開始時(shí)的 pageY,表示手指向下滑動(dòng),對(duì)應(yīng)頁面向上滾動(dòng),反之亦然。
此處我們需要觸摸事件跟蹤觸摸的屬性:
touches:當(dāng)前跟蹤的觸摸操作的 Touch 對(duì)象的數(shù)組,用于獲取觸摸開始時(shí)的 pageY 值;
changeTouches:自上次觸摸以來發(fā)生了改變的 Touch 對(duì)象的數(shù)組,用于獲取觸摸觸摸結(jié)束時(shí)的 pageY 值。
相關(guān)代碼如下:
// 手指接觸屏幕 document.addEventListener("touchstart", event => { this.startY = event.touches[0].pageY; }); //手指離開屏幕 document.addEventListener("touchend", event => { let endY = event.changedTouches[0].pageY; if (endY - this.startY < 0) { // 手指向上滑動(dòng),對(duì)應(yīng)頁面向下滾動(dòng) this.goDown(); } else { // 手指向下滑動(dòng),對(duì)應(yīng)頁面向上滾動(dòng) this.goUp(); } });
為了避免下拉刷新,可以阻止 touchmove 事件的默認(rèn)行為:
// 阻止 touchmove 下拉刷新 document.addEventListener("touchmove", event => { event.preventDefault(); });6)PC 端滾動(dòng)事件性能優(yōu)化 6.1 防抖函數(shù)和截流函數(shù)介紹
優(yōu)化主要從兩方便入手:
更改頁面大小時(shí),通過防抖動(dòng)(debounce)函數(shù)限制 resize 事件觸發(fā)頻率;
滾動(dòng)/滑動(dòng)事件觸發(fā)時(shí),通過截流(throttle)函數(shù)限制滾動(dòng)/滑動(dòng)事件觸發(fā)頻率。
既然都是限制觸發(fā)頻率(都通過定時(shí)器實(shí)現(xiàn)),那這兩者有什么區(qū)別?
首先,防抖動(dòng)函數(shù)工作時(shí),如果在指定的延遲時(shí)間內(nèi),某個(gè)事件連續(xù)觸發(fā),那么綁定在這個(gè)事件上的回調(diào)函數(shù)永遠(yuǎn)不會(huì)觸發(fā),只有在延遲時(shí)間內(nèi),這個(gè)事件沒再觸發(fā),對(duì)應(yīng)的回調(diào)函數(shù)才會(huì)執(zhí)行。防抖動(dòng)函數(shù)非常適合改變窗口大小這一事件,這也符合 拖動(dòng)到位以后再觸發(fā)事件,如果一直拖個(gè)不停,始終不觸發(fā)事件 這一直覺。
而截流函數(shù)是在延遲時(shí)間內(nèi),綁定到事件上的回調(diào)函數(shù)能且只能觸發(fā)一次,這和截流函數(shù)不同,即便是在延遲時(shí)間內(nèi)連續(xù)觸發(fā)事件,也不會(huì)阻止在延遲時(shí)間內(nèi)有一個(gè)回調(diào)函數(shù)執(zhí)行。并且截流函數(shù)允許我們指定回調(diào)函數(shù)是在延遲時(shí)間開始時(shí)還是結(jié)束時(shí)執(zhí)行。
鑒于截流函數(shù)的上述兩個(gè)特性,尤其適合優(yōu)化滾動(dòng)/滑動(dòng)事件:
可以限制頻率;
不會(huì)因?yàn)闈L動(dòng)/滑動(dòng)事件太靈敏(在延遲時(shí)間內(nèi)不斷觸發(fā))導(dǎo)致注冊(cè)在事件上的回調(diào)函數(shù)無法執(zhí)行;
可以設(shè)置在延遲時(shí)間開始時(shí)觸發(fā)回調(diào)函數(shù),從而避免用戶感到操作之后的短暫延時(shí)。
這里不介紹防抖動(dòng)函數(shù)和截流函數(shù)的實(shí)現(xiàn)原理,感興趣的可以看Throttling and Debouncing in JavaScript,下面是實(shí)現(xiàn)的代碼:
// 防抖動(dòng)函數(shù),method 回調(diào)函數(shù),context 上下文,event 傳入的時(shí)間,delay 延遲函數(shù) debounce(method, context, event, delay) { clearTimeout(method.tId); method.tId = setTimeout(() => { method.call(context, event); }, delay); }, // 截流函數(shù),method 回調(diào)函數(shù),context 上下文,delay 延遲函數(shù), // 這里沒有提供是在延遲時(shí)間開始還是結(jié)束的時(shí)候執(zhí)行回調(diào)函數(shù)的選項(xiàng), // 直接在延遲時(shí)間開始的時(shí)候執(zhí)行回調(diào) throttle(method, context, delay) { let wait = false; return function() { if (!wait) { method.apply(context, arguments); wait = true; setTimeout(() => { wait = false; }, delay); } }; },
《JavaScript 高級(jí)程序設(shè)計(jì) - 第三版》 22.33.3 節(jié)中介紹的 throttle 函數(shù)和此處定義的不同,高程中定義的 throttle 函數(shù)對(duì)應(yīng)此處的 debounce 函數(shù),但網(wǎng)上大多數(shù)文章都和高程中的不同,比如 lodash 中定義的 debounce。6.2 改造 PC 端滾動(dòng)事件
通過上述說明,我們已經(jīng)知道截流函數(shù)可以通過限定滾動(dòng)事件觸發(fā)頻率提升性能,同時(shí),設(shè)置在延遲時(shí)間開始階段立即調(diào)用滾動(dòng)事件的回調(diào)函數(shù)并不會(huì)犧牲用戶體驗(yàn)。
截流函數(shù)上文已經(jīng)定義好,使用起來就很簡(jiǎn)單了:
// 設(shè)置截流函數(shù) let handleMouseWheel = utils.throttle(this.scrollMouse, this, this.DELAY, true); // 鼠標(biāo)滾輪監(jiān)聽,火狐鼠標(biāo)滾動(dòng)事件不同其他 if (navigator.userAgent.toLowerCase().indexOf("firefox") === -1) { document.addEventListener("mousewheel", handleMouseWheel); } else { document.addEventListener("DOMMouseScroll", handleMouseWheel); }
上面這部分代碼是寫在 class 的 init 方法中,所以截流函數(shù)的上下文(context)傳入的是 this,表示當(dāng)前 class 實(shí)例。
7)其他 7.1 導(dǎo)航按鈕為了簡(jiǎn)化 html 結(jié)構(gòu),導(dǎo)航按鈕通過 js 創(chuàng)建。這里的難點(diǎn)在于如何實(shí)現(xiàn)點(diǎn)擊不同按鈕實(shí)現(xiàn)對(duì)應(yīng)頁面的跳轉(zhuǎn)并更新對(duì)應(yīng)按鈕的樣式。
解決的思路是:
頁面跳轉(zhuǎn):頁面?zhèn)€數(shù)和導(dǎo)航按鈕的個(gè)數(shù)一致,所以點(diǎn)擊第 i 個(gè)按鈕也就是跳轉(zhuǎn)到第 i 個(gè)頁面,而第 i 個(gè)頁面對(duì)應(yīng)的容器 top 值恰好是 -(i * this.viewHeight)
更改樣式:更改樣式即先刪除所有按鈕的選中樣式,然后給當(dāng)前點(diǎn)擊的按鈕添加選中樣式。
// 創(chuàng)建右側(cè)點(diǎn)式導(dǎo)航 createNav() { const nav = document.createElement("div"); nav.className = "nav"; this.container.appendChild(nav); // 有幾頁,顯示幾個(gè)點(diǎn) for (let i = 0; i < this.pagesNum; i++) { nav.innerHTML += "7.2 自定義參數(shù) "; } const navDots = document.querySelectorAll(".nav-dot"); this.navDots = Array.prototype.slice.call(navDots); // 添加初始樣式 this.navDots[0].classList.add("active"); // 添加點(diǎn)式導(dǎo)航點(diǎn)擊事件 this.navDots.forEach((el, i) => { el.addEventListener("click", event => { // 頁面跳轉(zhuǎn) this.currentPosition = -(i * this.viewHeight); this.turnPage(this.currentPosition); // 更改樣式 this.navDots.forEach(el => { utils.deleteClassName(el, "active"); }); event.target.classList.add("active"); }); }); }
得當(dāng)?shù)淖远x參數(shù)可以增加插件的靈活性。
參數(shù)通過構(gòu)造函數(shù)傳入,并通過 Object.assign() 進(jìn)行參數(shù)合并:
constructor(options) { // 默認(rèn)配置 const defaultOptions = { isShowNav: true, delay: 150, definePages: () => {}, }; // 合并自定義配置 this.options = Object.assign(defaultOptions, options); }7.3 窗口尺寸改變時(shí)更新數(shù)據(jù)
瀏覽器窗口尺寸改變的時(shí)候,需要重新獲取可視區(qū)、頁面元素高度,并重新確定容器當(dāng)前的 top 值。
同時(shí),為了避免不必要的性能開支,這里使用了防抖動(dòng)函數(shù)。
// window resize 時(shí)重新獲取位置 getNewPosition() { this.viewHeight = document.documentElement.clientHeight; this.container.style.height = this.viewHeight + "px"; let activeNavIndex; this.navDots.forEach((e, i) => { if (e.classList.contains("active")) { activeNavIndex = i; } }); this.currentPosition = -(activeNavIndex * this.viewHeight); this.turnPage(this.currentPosition); } handleWindowResize(event) { // 設(shè)置防抖動(dòng)函數(shù) utils.debounce(this.getNewPosition, this, event, this.DELAY); } // 窗口尺寸變化時(shí)重置位置 window.addEventListener("resize", this.handleWindowResize.bind(this));7.4 兼容性
這里的兼容性主要指兩個(gè)方面:一是不同瀏覽器對(duì)同一行為定義了不同 API,比如上文提到的獲取鼠標(biāo)滾動(dòng)信息的 API Firefox 和其他瀏覽器不一樣;第二點(diǎn)就是 ES6 新語法、新 API 的兼容處理。
對(duì)于 class、箭頭函數(shù)這類新語法的轉(zhuǎn)換,通過 babel 就可完成,鑒于本插件代碼量很小,都處于可控的狀態(tài),并沒有引入 babel 提供的 polyfill 方案,因?yàn)樾?API 只有 Object.assign() 需要做兼容處理,多帶帶寫個(gè) polyfill 就好,如下:
// polyfill Object.assign polyfill() { if (typeof Object.assign != "function") { Object.defineProperty(Object, "assign", { value: function assign(target, varArgs) { if (target == null) { throw new TypeError("Cannot convert undefined or null to object"); } let to = Object(target); for (let index = 1; index < arguments.length; index++) { let nextSource = arguments[index]; if (nextSource != null) { for (let nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true, }); } },
引用自:MDN-Object.assign()
因?yàn)楸静寮患嫒莸?IE10,所以不打算對(duì)事件做兼容處理,畢竟 IE9 都支持 addEventListener 了。7.5 通過惰性載入進(jìn)一步優(yōu)化性能
在 5.1 中寫的 getWheelDelta 函數(shù)每次執(zhí)行都需要檢測(cè)是否支持 event.wheelDelta,實(shí)際上,瀏覽器只需在第一次加載時(shí)檢測(cè),如果支持,接下來都會(huì)支持,再做檢測(cè)是沒必要的。
并且這個(gè)檢測(cè)在頁面的生命周期中會(huì)執(zhí)行很多次,這種情況下可以通過 惰性載入 技巧進(jìn)行優(yōu)化,如下:
getWheelDelta(event) { if (event.wheelDelta) { // 第一次調(diào)用之后惰性載入,無需再做檢測(cè) this.getWheelDelta = event => event.wheelDelta; // 第一次調(diào)用使用 return event.wheelDelta; } else { // 兼容火狐 this.getWheelDelta = event => -event.detail; return -event.detail; } },
完整源碼在這 pure_full_page,點(diǎn)這查看 demo。
參考資料純 JS 全屏滾動(dòng) / 整屏翻頁
Throttling and Debouncing in JavaScript
Debouncing and Throttling Explained Through Examples
JavaScript Debounce Function
Simple throttle in js
Simple throttle in js - jsfiddle
Viewport height is taller than the visible part of the document in some mobile browsers
MDN-Object.assign()
Babel 編譯出來還是 ES 6?難道只能上 polyfill?- Henry 的回答
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107730.html
摘要:由此,我們可以完全屏蔽的默認(rèn)滾動(dòng)觸發(fā),改用方法控制全屏滾動(dòng),解除了全屏滾動(dòng)與鼠標(biāo)滾輪事件的強(qiáng)耦合。此外,通過定時(shí)器延時(shí)秒設(shè)置的值,將用戶的鼠標(biāo)滾輪操作強(qiáng)制分為兩步,最終實(shí)現(xiàn)了目的。 需求分析 剛進(jìn)公司產(chǎn)品提出一個(gè)需求:在全屏頁面中滾動(dòng)鼠標(biāo)滾輪更新文本,回滾再恢復(fù)原文本,同時(shí)不影響全屏頁面的正常切換: 初始狀態(tài) showImg(https://segmentfault.com/img/b...
原文鏈接:https://github.com/AlloyTeam/AlloyTouch/wiki/AlloyTouch-FullPage-Plugin 先驗(yàn)貨 showImg(https://segmentfault.com/img/remote/1460000007885626?w=280&h=280); 插件代碼可以在這里找到。 注意,雖然是掃碼體驗(yàn),但是AlloyTouch.FullPag...
摘要:轉(zhuǎn)載來源包管理器管理著庫,并提供讀取和打包它們的工具。能構(gòu)建更好應(yīng)用的客戶端包管理器。一個(gè)整合和的最佳思想,使開發(fā)者能快速方便地組織和編寫前端代碼的下一代包管理器。很棒的組件集合。隱秘地使用和用戶數(shù)據(jù)。 轉(zhuǎn)載來源:https://github.com/jobbole/aw... 包管理器管理著 javascript 庫,并提供讀取和打包它們的工具。?npm – npm 是 javasc...
摘要:轉(zhuǎn)載來源包管理器管理著庫,并提供讀取和打包它們的工具。能構(gòu)建更好應(yīng)用的客戶端包管理器。一個(gè)整合和的最佳思想,使開發(fā)者能快速方便地組織和編寫前端代碼的下一代包管理器。很棒的組件集合。隱秘地使用和用戶數(shù)據(jù)。 轉(zhuǎn)載來源:https://github.com/jobbole/aw... 包管理器管理著 javascript 庫,并提供讀取和打包它們的工具。?npm – npm 是 javasc...
閱讀 1650·2021-11-11 10:59
閱讀 2657·2021-09-04 16:40
閱讀 3702·2021-09-04 16:40
閱讀 3021·2021-07-30 15:30
閱讀 1723·2021-07-26 22:03
閱讀 3193·2019-08-30 13:20
閱讀 2263·2019-08-29 18:31
閱讀 469·2019-08-29 12:21