主頁 > 企業開發 > 前端也要懂物理 —— 慣性滾動篇

前端也要懂物理 —— 慣性滾動篇

2020-09-18 01:38:27 企業開發

HEADER
作者:凹凸曼-吖偉

我們在平時編程開發時,除了需要關注技術實作、演算法、代碼效率等因素之外,更要把所學到的學科知識(如物理學、理論數學等等)靈活應用,畢竟理論和實踐相輔相成、密不可分,這無論是對于我們的方案選型、還是技術實踐理解都有非常大的幫助,今天就讓我們一起來回顧中學物理知識,并靈活運用到慣性滾動的動效實作當中,

慣性滾動(也叫 滾動回彈momentum-based scrolling)最早是出現在 iOS 系統中,是指 當用戶在終端上滑動頁面然后把手指挪開,頁面不會馬上停下而是繼續保持一定時間的滾動效果,并且滾動的速度和持續時間是與滑動手勢的強烈程度成正比,抽象地理解,就像高速行駛的列車制動后依然會往前行駛一段距離才會最終停下,而且在 iOS 系統中,當頁面滾動到頂/底部時,還有可能觸發 “回彈” 的效果,這里錄制了微信 APP 【賬單】頁面中的 iOS 原生時間選擇器的慣性滾動效果:

微信原生 date-picker

熟悉 CSS 開發的同學或許會知道,在 Safari 瀏覽器中有這樣一條 CSS 規則:

-webkit-overflow-scrolling: touch;

當其樣式值為 touch 時,瀏覽器會使用具有回彈效果的滾動, 即“當手指從觸摸屏上移開,內容會繼續保持一段時間的滾動效果”,除此之外,在豐富多姿的 web 前端生態中,很多經典組件的互動都一定程度地沿用了慣性滾動的效果,譬如下面提到的幾個流行 H5 組件庫中的例子,

流行 UI 庫效果

為了方便對比,我們先來看看一個 H5 普通長串列在 iOS 系統下(開啟了滾動回彈)的滾動表現:

iOS 下長串列滾動表現

  • weui 的 picker 組件

    weui picker

    明顯可見,weui 選擇器的慣性滾動效果非常弱,基本上手從螢屏上移開后滾動就很快停止了,體驗較為不好,

  • vant 的 picker 組件

    vant picker

    相比之下,vant 選擇器的慣性滾動效果則明顯清晰得多,但是由于觸頂/底回彈時依然維持了普通滾動時的系數或持續時間,導致整體來說回彈的效果有點脫節,

應用物理學模型

慣性 一詞來源于物理學中的慣性定律(即 牛頓第一定律):一切物體在沒有受到力的作用的時候,運動狀態不會發生改變,物體所擁有的這種性質就被稱為慣性,可想而知,慣性滾動的本質就是物理學中的慣性現象,因此,我們可以恰當利用中學物理上的 滑塊模型 來描述慣性滾動全程序,

為了方便描述,我們把瀏覽器慣性滾動效果中的滾動目標(如瀏覽器中的頁面元素)模擬成滑塊模型中的 滑塊,而且分析得出,慣性滾動的全程序可以模擬為(人)使滑塊滑動一定距離然后釋放的程序,那么,全流程可以拆解為以下兩個階段:

  • 第一階段,滑動滑塊使其從靜止開始做加速運動;

    滑塊模型第一階段

    在此階段,滑塊受到的 F拉 大于 F摩 使其從左到右勻加速前進,

    需要注意的是,對于瀏覽器的慣性滾動來說,我們一般關注的是用戶即將釋放手指前的一小階段,而非滾動的全流程(全流程意義不大),這一瞬間階段可以簡單模擬為滑塊均衡受力做 勻加速運動,

  • 第二階段,釋放滑塊使其在只受摩擦力的作用下繼續滑動,直至最終靜止;

    滑塊模型第二階段

    在此階段,滑塊只受到反向的摩擦力,會維持從左到右的運動方向減速前進然后停下,

基于滑塊模型,我們需要找到適合的量化指標來建立慣性滾動的計算體系,結合模型和具體實作,我們需要關注 滾動距離速度曲線 以及 滾動時長 這幾個關鍵指標,下面會一一展開決議,

滾動距離

對于滑動模型的第一階段,滑塊做勻加速運動,我們不妨設滑塊的滑動距離為 s1,滑動的時間為 t1,結束時的臨界點速度(末速度)為 v1 ,根據位移公式

位移公式

可以得出速度關系

第一階段末速度

對于第二階段,滑塊受摩擦力 F拉 做勻減速運動,我們不妨設滑動距離為 s2,滑動的時間為 t2,滑動加速度為 a,另外初速度為 v1,末速度為 0m/s,結合位移公式和加速度公式

加速度公式

可以推算出滑動距離 s2

第二階段滑動距離

由于勻減速運動的加速度為負(即 a < 0),不妨設一個加速度常量 A,使其滿足 A = -2a 的關系,那么滑動距離

第二階段滑動距離和常量關系

然而在瀏覽器實際應用時,v1 算平方會導致最終計算出的慣性滾動距離太大(即對滾動手勢的強度感應過于靈敏),我們不妨把平方運算去掉

兩階段關系

所以,求慣性滾動的距離(即 s2)時,我們只需要記錄用戶滾動的 距離 s1滾動時長 t1,并設定一個合適的 加速度常量 A 即可,

經大量測驗得出,加速度常量 A 的合適值為 0.003

另外,需要注意的是,對于真正的瀏覽器慣性滾動效果來說,這里討論的滾動距離和時長是指能夠作用于慣性滾動的范圍內的距離和時長,而非用戶滾動頁面元素的全流程,詳細的可以看【啟停條件】這一節內容,

慣性滾動速度曲線

針對慣性滾動階段,也就是第二階段中的勻減速運動,根據位移公式可以得到位移差和時間間距 T 的關系

位移差和時間關系

不難得出,在同等時間間距條件下,相鄰兩段位移差會越來越小,換句話說就是慣性滾動的偏移量增加速度會越來越小,這與 CSS3 transition-timing-function 中的 ease-out 速度曲線非常吻合,ease-out (即 cubic-bezier(0, 0, .58, 1))的貝塞爾曲線為

ease-out 貝塞爾曲線

曲線圖來自 在線繪制貝塞爾曲線網站,

其中,圖表中的縱坐標是指 影片推進的行程,橫坐標是指 時間,原點坐標為 (0, 0),終點坐標為 (1, 1),假設影片持續時間為 2 秒,(1, 1) 坐標點則代表影片啟動后 2 秒時影片執行完畢(100%),根據圖表可以得出,時間越往后影片行程的推進速度越慢,符合勻減速運動的特性,

我們試試實踐應用 ease-out 速度曲線:

ease-out 曲線應用

很明顯,這樣的速度曲線過于線性平滑,減速效果不明顯,我們參考 iOS 滾動回彈的效果重復測驗,調整貝塞爾曲線的引數為 cubic-bezier(.17, .89, .45, 1)

調整后的貝塞爾曲線

調整曲線后的效果理想很多:

調整后的曲線效果

回彈

接下來模擬慣性滾動時觸碰到容器邊界觸發回彈的情況,

我們基于滑塊模型來模擬這樣的場景:滑塊左端與一根彈簧連接,彈簧另一端固定在墻體上,在滑塊向右滑動的程序中,當滑塊到達臨界點(彈簧即將發生形變時)而速度還沒有降到 0m/s 時,滑塊會繼續滑動并拉動彈簧使其發生形變,同時滑塊會受到彈簧的反拉力作減速運動(動能轉化為內能);當滑塊速度降為 0m/s 時,此時彈簧的形變數最大,由于彈性特質彈簧會恢復原狀(內能轉化成動能),并拉動滑塊反向(左)運動,

類似地,回彈程序也可以分為下面兩個階段:

  • 滑塊拉動彈簧往右做變減速運動;

    回彈第一階段模型

此階段滑塊受到摩擦力 F摩 和越來越大的彈簧拉力 F彈 共同作用,加速度越來越大,導致速度降為 0m/s 的時間會非常短,

  • 彈簧恢復原狀,拉動滑塊向左做先變加速后變減速運動;

    回彈第二階段模型

    此階段滑塊受到的摩擦力 F摩 和越來越小的彈簧拉力 F彈 相互抵消,剛開始 F彈 > F摩,滑塊做加速度越來越小的變加速運動;隨后 F彈 < F摩,滑塊做加速度越來越大的變減速運動,直至最終靜止,這里為了方便實際計算,我們不妨假設一個理想狀態:當滑塊靜止時彈簧剛好恢復形變

回彈距離

根據上面的模型分析,回彈的第一階段做加速度越來越大的變減速直線運動,不妨設此階段的初速度為 v0,末速度為 v1,那么可以與滑塊位移建立關系:

回彈第一階段位移

其中 a 為加速度變數,這里暫不展開討論,那么,根據物理學的彈性模型,第二階段的回彈距離為

回彈第二階段位移

微積分都來了,簡直沒法計算……

然而,我們可以根據運動模型適當簡化 S回彈 值的計算,由于 回彈第二階段的加速度 是大于 非回彈慣性滾動階段的加速度F彈 + F摩 > F摩)的,不妨設非回彈慣性滾動階段的總距離為 S滑,那么

回彈距離關系

因此,我們可以設定一個較為合理的常量 B,使其滿足這樣的等式:

回彈距離等式

經大量實踐得出,常量 B 的合理值為 10,

回彈速度曲線

觸發回彈的整個慣性滾動軌跡可以拆分成三個運動階段:

觸發回彈的運動軌跡

然而,如果要把階段 a 和階段 b 準確描繪成 CSS 影片是有很高的復雜度的:

  • 階段 b 中的變減速運動難以準確描繪;
  • 這兩個階段雖運動方向相同但影片速度曲線不連貫,很容易造成用戶體驗斷層;

為了簡化流程,我們把階段 ab 合并成一個運動階段,那么簡化后的軌跡就變成:

簡化后的回彈運動軌跡

鑒于在階段 a 末端的反向加速度會越來越大,所以此階段滑塊的速度驟減同比非回彈慣性滾動更快,對應的貝塞爾曲線末端就會更陡,我們選擇一條較為合理的曲線 cubic-bezier(.25, .46, .45, .94)

回彈階段 a 曲線

對于階段 b,滑塊先變加速后變減速,與 ease-in-out 的曲線有點類似,實踐嘗試:

ease-in-out 曲線實踐

仔細觀察,我們發現階段 a 和階段 b 的銜接不夠流暢,這是由于 ease-in-out 曲線的前半段緩入導致的,所以,為了突出效果我們選擇只描繪變減速運動的階段 b 末段,貝塞爾曲線調整為 cubic-bezier(.165, .84, .44, 1)

調整后的貝塞爾曲線

實踐效果:

調整后的貝塞爾曲線實踐

由于 gif 轉格式導致部分掉幀,示例效果看起來會有點卡頓,建議直接體驗 demo

CSS 動效時長

我們對 iOS 的滾動回彈效果做多次測量,定義出體驗良好的動效時長引數,在一次慣性滾動中,可能會出現下面兩種情況,對應的動效時間也不一樣:

  • 沒有觸發回彈

    慣性滾動的合理持續時間為 2500ms

  • 觸發回彈

    對于階段 a,當 S回彈 大于某個關鍵閾值時定義為 強回彈,動效時長為 400ms;反之則定義為 榷訓彈,動效時長為 800ms

    而對于階段 b,反彈的持續時間為 500ms 較為合理,

啟停條件

前文中有提到,如果把用戶滾動頁面元素的整個程序都納入計算范圍是非常不合理的,不難想象,當用戶以非常緩慢的速度使元素滾動比較大的距離,這種情況下元素動量非常小,理應不觸發慣性滾動,因此,慣性滾動的觸發是有條件的,

  • 啟動條件

    慣性滾動的啟動需要有足夠的動量,我們可以簡單地認為,當用戶滾動的距離足夠大(大于 15px)和持續時間足夠短(小于 300ms)時,即可產生慣性滾動,換成編程語言就是,最后一次 touchmove 事件觸發的時間和 touchend 事件觸發的時間間隔小于 300ms,且兩者產生的距離差大于 15px 時認為可啟動慣性滾動,

  • 暫停時機

    當慣性滾動未結束(包括處于回彈程序),用戶再次觸碰滾動元素時,我們應該暫停元素的滾動,在實作原理上,我們需要通過 getComputedStylegetPropertyValue 方法獲取當前的 transform: matrix() 矩陣值,抽離出元素的水平 y 軸偏移量后重新調整 translate 的位置,

示例代碼

基于 vuejs 提供了部分關鍵代碼,也可以直接訪問 codepen demo 體驗效果(完整代碼),

<html>
  <body>
    <div id="app"></div>
    <template id="tpl">
      <div
        ref="wrapper"
        @touchstart.prevent="onStart"
        @touchmove.prevent="onMove"
        @touchend.prevent="onEnd"
        @touchcancel.prevent="onEnd"
        @transitionend="onTransitionEnd">
        <ul ref="scroller" :style="scrollerStyle">
          <li v-for="item in list">{{item}}</li>
        </ul>
      </div>
    </template>
    <script>
      new Vue({
        el: '#app',
        template: '#tpl',
        computed: {
          list() {},
          scrollerStyle() {
            return {
              'transform': `translate3d(0, ${this.offsetY}px, 0)`,
              'transition-duration': `${this.duration}ms`,
              'transition-timing-function': this.bezier,
            };
          },
        },
        data() {
          return {
            minY: 0,
            maxY: 0,
            wrapperHeight: 0,
            duration: 0,
            bezier: 'linear',
            pointY: 0,                    // touchStart 手勢 y 坐標
            startY: 0,                    // touchStart 元素 y 偏移值
            offsetY: 0,                   // 元素實時 y 偏移值
            startTime: 0,                 // 慣性滑動范圍內的 startTime
            momentumStartY: 0,            // 慣性滑動范圍內的 startY
            momentumTimeThreshold: 300,   // 慣性滑動的啟動 時間閾值
            momentumYThreshold: 15,       // 慣性滑動的啟動 距離閾值
            isStarted: false,             // start鎖
          };
        },
        mounted() {
          this.$nextTick(() => {
            this.wrapperHeight = this.$refs.wrapper.getBoundingClientRect().height;
            this.minY = this.wrapperHeight - this.$refs.scroller.getBoundingClientRect().height;
          });
        },
        methods: {
          onStart(e) {
            const point = e.touches ? e.touches[0] : e;
            this.isStarted = true;
            this.duration = 0;
            this.stop();
            this.pointY = point.pageY;
            this.momentumStartY = this.startY = this.offsetY;
            this.startTime = new Date().getTime();
          },
          onMove(e) {
            if (!this.isStarted) return;
            const point = e.touches ? e.touches[0] : e;
            const deltaY = point.pageY - this.pointY;
            this.offsetY = Math.round(this.startY + deltaY);
            const now = new Date().getTime();
            // 記錄在觸發慣性滑動條件下的偏移值和時間
            if (now - this.startTime > this.momentumTimeThreshold) {
              this.momentumStartY = this.offsetY;
              this.startTime = now;
            }
          },
          onEnd(e) {
            if (!this.isStarted) return;
            this.isStarted = false;
            if (this.isNeedReset()) return;
            const absDeltaY = Math.abs(this.offsetY - this.momentumStartY);
            const duration = new Date().getTime() - this.startTime;
            // 啟動慣性滑動
            if (duration < this.momentumTimeThreshold && absDeltaY > this.momentumYThreshold) {
              const momentum = this.momentum(this.offsetY, this.momentumStartY, duration);
              this.offsetY = Math.round(momentum.destination);
              this.duration = momentum.duration;
              this.bezier = momentum.bezier;
            }
          },
          onTransitionEnd() {
            this.isNeedReset();
          },
          momentum(current, start, duration) {
            const durationMap = {
              'noBounce': 2500,
              'weekBounce': 800,
              'strongBounce': 400,
            };
            const bezierMap = {
              'noBounce': 'cubic-bezier(.17, .89, .45, 1)',
              'weekBounce': 'cubic-bezier(.25, .46, .45, .94)',
              'strongBounce': 'cubic-bezier(.25, .46, .45, .94)',
            };
            let type = 'noBounce';
            // 慣性滑動加速度
            const deceleration = 0.003;
            // 回彈阻力
            const bounceRate = 10;
            // 強榷訓彈的分割值
            const bounceThreshold = 300;
            // 回彈的最大限度
            const maxOverflowY = this.wrapperHeight / 6;
            let overflowY;

            const distance = current - start;
            const speed = 2 * Math.abs(distance) / duration;
            let destination = current + speed / deceleration * (distance < 0 ? -1 : 1);
            if (destination < this.minY) {
              overflowY = this.minY - destination;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.max(this.minY - maxOverflowY, this.minY - overflowY / bounceRate);
            } else if (destination > this.maxY) {
              overflowY = destination - this.maxY;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.min(this.maxY + maxOverflowY, this.maxY + overflowY / bounceRate);
            }

            return {
              destination,
              duration: durationMap[type],
              bezier: bezierMap[type],
            };
          },
          // 超出邊界時需要重置位置
          isNeedReset() {
            let offsetY;
            if (this.offsetY < this.minY) {
              offsetY = this.minY;
            } else if (this.offsetY > this.maxY) {
              offsetY = this.maxY;
            }
            if (typeof offsetY !== 'undefined') {
              this.offsetY = offsetY;
              this.duration = 500;
              this.bezier = 'cubic-bezier(.165, .84, .44, 1)';
              return true;
            }
            return false;
          },
          // 停止滾動
          stop() {
            const matrix = window.getComputedStyle(this.$refs.scroller).getPropertyValue('transform');
            this.offsetY = Math.round(+matrix.split(')')[0].split(', ')[5]);
          },
        },
      });
    </script>
  </body>
</html>

參考資料

  • weui-picker
  • better-scroll

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

歡迎關注凹凸實驗室公眾號

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/69801.html

標籤:JavaScript

上一篇:Js--字串拼接/連接

下一篇:1年轉行資深前端工程師,開源專案過 1k stars,完整學習程序

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more