最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3封裝自動(dòng)滾動(dòng)列表指令(含網(wǎng)頁(yè)縮放滾動(dòng)問(wèn)題)

 更新時(shí)間:2023年05月23日 10:47:11   作者:xzxldl  
本文主要介紹了Vue3封裝自動(dòng)滾動(dòng)列表指令(含網(wǎng)頁(yè)縮放滾動(dòng)問(wèn)題),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

長(zhǎng)列表自動(dòng)滾動(dòng)跟banner自動(dòng)切換一樣,是一個(gè)C端展示頁(yè)面經(jīng)常遇到的需求。不過(guò)網(wǎng)上各類組件庫(kù)基本都對(duì)“幻燈片”(banner使用的組件)組件有封裝,自帶好自動(dòng)切換的功能,而長(zhǎng)列表(或表格等)自動(dòng)滾動(dòng)的功能則涉及甚少。

這里分享一個(gè)我項(xiàng)目中封裝的自動(dòng)滾動(dòng)指令,且附帶期間解決了頁(yè)面縮放導(dǎo)致滾動(dòng)失效的解決思路與方案:

需求定義

首先,我們需要理清這個(gè)通用指令的需求點(diǎn),方便進(jìn)行下一步設(shè)計(jì)。這里需要的需求點(diǎn)有:

  • 支持任意組件/標(biāo)簽的滾動(dòng)。
  • 可控制滾動(dòng)速度。
  • 可設(shè)置鼠標(biāo)移入后停止?jié)L動(dòng)。

大致思路設(shè)計(jì)

首先,滿足第一點(diǎn),即指令可以在任意組件(包括原生的與自己封裝的Vue組件)上使用,考慮到滾動(dòng)相關(guān)的API,初步確定,我們需要使用到:

scrollTo, scrollHeight, clientHeight

分別用來(lái)設(shè)置滾動(dòng)高度,獲取最大滾動(dòng)高度 與 可視區(qū)域高度。

這一點(diǎn)很好滿足,任何通過(guò)我們上述的組件獲取到的DOM(HTMLElement)都支持這三個(gè)接口/屬性。

其次是速度控制,使用數(shù)字來(lái)控制每幀滾動(dòng)的距離(px),我們可以將速度作為指令的參數(shù)。

最后是可選的鼠標(biāo)移入暫停功能,boolean類型的選項(xiàng),很容易就能想到使用指令修飾符。

綜上,我們的指令調(diào)用方式需要滿足以下:

<element v-scroll.mouse="1.5"></element>

編碼實(shí)現(xiàn)

1. 指令注冊(cè)和整體“架構(gòu)”代碼

/**
 * main.ts
 */
import scrollDirective from '@/directive/scroll.ts';
...
const app = createApp(App);
// 注冊(cè)指令
app.directive(scrollDirective.name, scrollDirective);
...
/**
 * scroll.ts
 */
 import { DirectiveBinding } from 'vue';
 export default {
     name: 'scroll',
     mounted: (el: HTMLElement, binding: DirectiveBinding) => {},
     unmounted: (el: HTMLElement, binding: DirectiveBinding) => {}
 }

2. 速度控制以及滾動(dòng)

JS編寫(xiě)動(dòng)畫(huà),本能的就想到RAF能夠最好的實(shí)現(xiàn)動(dòng)畫(huà)效果,RAF并在性能與視覺(jué)效果之間自動(dòng)做好權(quán)衡,就是你了。

將速度speed作為指令參數(shù),在動(dòng)畫(huà)函數(shù)中,用當(dāng)前 scrollTop 累加 speed 來(lái)實(shí)現(xiàn)滾動(dòng)效果。

/**
 * scroll.ts 
 */ 
import { DirectiveBinding } from 'vue';
// 我們會(huì)在DOM上拓展一些屬性用于滾動(dòng)動(dòng)畫(huà)的執(zhí)行,這里拓展一下類型,方便編碼
interface AnimationElement extends HTMLElement {
    speed: number;
}
const DEFAULT_SPEED = 1;
const RAF = window.requestAnimationFrame;
const CancelRAF = window.cancelAnimationFrame;
const elementScroll = (el: AnimationElement) => {
    const maxScrollTop = el.scrollHeight - el.clientHeight;
    // 根據(jù)當(dāng)前滾動(dòng)高度與滾動(dòng)速度,計(jì)算出新的滾動(dòng)高度
    const scrollTop =
        el.scrollTop + el.speed >= maxScrollTop // 超過(guò)最大滾動(dòng)高度重置 --〉 從頭再來(lái)
            ? 0
            : el.scrollTop + el.speed;
    // 執(zhí)行滾動(dòng)
    el.scrollTo({
        top: scrollTop
    });
    // 繼續(xù)執(zhí)行下一幀動(dòng)畫(huà)
    RAF(elementScroll.bind(null, el));
}
export default {
    name: 'scroll',
    mounted: (el: AnimationElement, binding: DirectiveBinding) => {
        const maxScrollTop = el.scrollHeight - el.clientHeight;
        // 沒(méi)有滾動(dòng)空間的時(shí)候,無(wú)需滾動(dòng),直接返回。
        if (maxScrollTop <= 0) {
            return;
        }
        // 將速度變量存到DOM中,方便后續(xù)動(dòng)畫(huà)函數(shù)取用
        el.speed = binding.value || DEFAULT_SPEED;
        // 使用RAF調(diào)用動(dòng)畫(huà)函數(shù)
        RAF(elementScroll.bind(null, el));
    },
    unmounted: (el: HTMLElement, binding: DirectiveBinding) => {}
}

我們隨便寫(xiě)一個(gè)列表來(lái)試試

<template>
    <ul v-scroll class="w-[300px] h-[400px] overflow-auto bg-[darkcyan]">
        <li>1</li>
        <li>2</li>
        ...
    </ul>
</template>

看看效果,perfect!

scroll.gif

可以修改一下速度,讓他滾快點(diǎn)兒

<ul v-scroll="2" ...>

Nice,沒(méi)毛病。

scrollFast.gif

3. 鼠標(biāo)移入暫停滾動(dòng),移出恢復(fù)滾動(dòng)

要實(shí)現(xiàn)這個(gè)功能有兩個(gè)要點(diǎn):

一是事件監(jiān)聽(tīng),鼠標(biāo)移入/移出容器時(shí),將動(dòng)畫(huà)暫停/重啟;

二是獲取到當(dāng)前容器滾動(dòng)動(dòng)畫(huà)id(RAF 返回的),鼠標(biāo)移入時(shí),使用 window.cancelAnimationFrame 暫停動(dòng)畫(huà)。

/**
 * scroll.ts 
 */ 
interface AnimationElement extends HTMLElement {
    speed: number;
    animationId: number;
}
...
const elementScroll = (el: AnimationElement) => {
    ...
    // 繼續(xù)執(zhí)行下一幀,并更新動(dòng)畫(huà)id
    el.animationId = RAF(elementScroll.bind(null, el));
}
// 鼠標(biāo)移入暫停
const mouseEnterHandler = (el: AnimationElement) => {
    if (el.animationId) {
        // 取消動(dòng)畫(huà)
        CancelRAF(el.animationId);
        el.animationId = undefined;
    }
};
// 鼠標(biāo)移出繼續(xù)運(yùn)行動(dòng)畫(huà)
const mouseLeaveHandler = (el: AnimationElement) =>
    (el.animationId = RAF(scrollElement.bind(null, el)));
export default {
    name: 'scroll',
    mounted: (el: AnimationElement, binding: DirectiveBinding) => {
        ...
        // 修改原來(lái)初始化運(yùn)行動(dòng)畫(huà)的語(yǔ)句,將RAF結(jié)果存到el中,方便暫停動(dòng)畫(huà)時(shí)使用
        el.animationId = RAF(scrollElement.bind(null, el));
        // 檢測(cè)是否傳遞修飾符,傳遞了監(jiān)聽(tīng)鼠標(biāo)移入移出動(dòng)畫(huà)
        if (binding.modifiers.mouse) {
            el.addEventListener('mouseenter', mouseEnterHandler.bind(null, el));
            el.addEventListener('mouseleave', mouseLeaveHandler.bind(null, el));
        }
    },
    unmounted: (el: HTMLElement, binding: DirectiveBinding) => {
        // 別忘了DOM解綁時(shí)解除事件監(jiān)聽(tīng)
        if (binding.modifiers.mouse) {
            el.removeEventListener(
                'mouseenter',
                mouseEnterHandler.bind(null, el)
            );
            el.removeEventListener(
                'mouseleave',
                mouseLeaveHandler.bind(null, el)
            );
        }
    }
}

淺寫(xiě)個(gè)demo看看效果

<template>
    <ul v-scroll.mouse class="w-[300px] h-[400px] overflow-auto bg-[darkcyan]">
        <li>1</li>
        <li>2</li>
        ...
    </ul>
</template>

make scroll perfect again!

scrollFastxx.gif

這樣我們就實(shí)現(xiàn)了一個(gè)可以控制滾動(dòng)速度,支持鼠標(biāo)移入暫停滾動(dòng)的通用滾動(dòng)指令了。

存在問(wèn)題

第一版就這樣上線使用了,但很快哈,啪的一下,我就發(fā)現(xiàn)了一些問(wèn)題:

  • 傳入小數(shù)時(shí),列表不滾動(dòng)。
  • 頁(yè)面縮放后,列表不滾動(dòng)。

1. 問(wèn)題原因探究

首先要想解決問(wèn)題,在不存在魔法的情況下,我們要先尋找問(wèn)題的原因。

既然小數(shù)速度無(wú)法滾動(dòng),那我們?cè)跒g覽器上測(cè)試一二

讓頁(yè)面向下滾動(dòng) 0.7, 結(jié)果發(fā)現(xiàn)還是 0 (⊙o⊙),所以我們下次累加的時(shí)候還是 0 + 0.5 無(wú)限循環(huán),一直是 0。

image.png

隨后,我翻閱了一遍 W3C 文檔,找到 scrollTo 函數(shù)相關(guān)部分,不過(guò)文檔并未直接說(shuō)top參數(shù)的處理會(huì)向下去整,反而 interface ScrollOptions 中的 top 正是 double 類型,這說(shuō)明他實(shí)際上是支持小數(shù)的哇,那這是為什么?

掃視了好幾遍之后,發(fā)現(xiàn)了一個(gè)頻頻出現(xiàn)的單詞 pixels,這些參數(shù)都是以像素為單位的。

像素,像素?像素...??!道爺我悟了!

可不是嘛,這哪來(lái)的 0.5 個(gè)像素嘛,這可不得取整?

順便,在翻閱文檔時(shí),也找到了,網(wǎng)頁(yè)縮放后滾動(dòng)失效(即使speed >= 1)的原因:W3C文檔VisualViewport中找到了這句話,滾動(dòng)高度會(huì)隨著頁(yè)面縮放變小。

image.png

我們?cè)?code>Chrome嘗試一下,看看是否屬實(shí):

現(xiàn)在正常大小網(wǎng)頁(yè)設(shè)置一下滾動(dòng)高度,并沒(méi)有什么問(wèn)題

image.png

隨后,縮放網(wǎng)頁(yè)到90%,馬上哈,Y軸的滾動(dòng)量就變成 0

image.png

再嘗試一下賦值其他的值,會(huì)發(fā)現(xiàn),縮放后設(shè)置滾動(dòng)高度后,其真實(shí)的滾動(dòng)量確實(shí)減少了,但不是按照我們樸素思維等比例減少的(具體怎么個(gè)算法,沒(méi)找到...)

image.png

不過(guò)知道這點(diǎn)就足夠了,在當(dāng)前情況下,想要實(shí)現(xiàn)我們要的小數(shù)級(jí)別的滾動(dòng)速度,那么我們必然不能直接基于 el.scrollTop 來(lái)滾動(dòng)了,必須有所變通。

2. 問(wèn)題解決:緩存計(jì)算

在哐哐哐一通嘗試下(css animation | 改用setTimeout,把間隔時(shí)間放長(zhǎng) | etc.),最終我想到了一個(gè)破費(fèi)科特的辦法,既能滿足我們的需求,又很簡(jiǎn)單不需要大量改動(dòng)現(xiàn)有代碼:

—— 緩存計(jì)算滾動(dòng)高度

顧名思義,即當(dāng)el.scrollTop不可靠的時(shí)候,那么就由我們自己來(lái)手動(dòng)管理滾動(dòng)高度,設(shè)置一個(gè)自定義的變量來(lái)對(duì)scrollTop進(jìn)行累加,這樣就規(guī)避掉了el.scrollTop“只會(huì)取整”(并不是),導(dǎo)致設(shè)置 0.5 速度后,el.scrollTop一直是0無(wú)法累加的問(wèn)題了。

同時(shí)由于 scrollTop 是我們自己進(jìn)行計(jì)算累加的,也不會(huì)受到網(wǎng)頁(yè)縮放的影響了,縮放后也能正常地進(jìn)行滾動(dòng)了。

這樣即使我們 speed = 0.5 也能夠正常“慢速滾動(dòng)”(本質(zhì)上非整數(shù)的幀滾動(dòng)高度相同,即達(dá)到了速度放慢的效果)

3. 修改后完整代碼

PS:需要特別注意的是,將基準(zhǔn)滾動(dòng)高度改為我們的自定義緩存滾動(dòng)高度后,用戶自行滾動(dòng)的事件是不會(huì)自動(dòng)同步到我們的緩存滾動(dòng)高度的,所以需要我們自己同步一下。

/**
 * 自動(dòng)滾動(dòng)
 *
 * 修飾符:
 * mouse 支持鼠標(biāo)移入移出暫停動(dòng)畫(huà)
 */
import { DirectiveBinding } from 'vue';
interface AnimationElement extends HTMLElement {
    speed: number;
    animationId: number;
    cacheScrollTop: number; // 存放我們緩存的scrollTop
}
const RAF = window.requestAnimationFrame;
const CancelRAF = window.cancelAnimationFrame;
const scrollElement = (el: AnimationElement) => {
    const maxScrollTop = el.scrollHeight - el.clientHeight;
    // 直接在緩存滾動(dòng)高度上進(jìn)行計(jì)算
    el.cacheScrollTop =
        el.cacheScrollTop + el.speed >= maxScrollTop
            ? 0
            : el.cacheScrollTop + el.speed;
    // 將緩存高度設(shè)置為當(dāng)前滾動(dòng)高度
    el.scrollTo({
        top: el.cacheScrollTop
    });
    // 執(zhí)行下一幀
    el.animationId = RAF(scrollElement.bind(null, el));
};
// 鼠標(biāo)移入暫停
const mouseEnterHandler = (el: AnimationElement) => {
    if (el.animationId) {
        CancelRAF(el.animationId);
        el.animationId = undefined;
    }
};
// 鼠標(biāo)移出繼續(xù)運(yùn)行
const mouseLeaveHandler = (el: AnimationElement) =>
    (el.animationId = RAF(scrollElement.bind(null, el)));
// 處理用戶的滾動(dòng)事件
const elementScrollHandler = (el: AnimationElement) =>
    (el.cacheScrollTop = el.scrollTop);
export default {
    name: 'scroll',
    mounted: (el: AnimationElement, binding: DirectiveBinding) => {
        const maxScrollTop = el.scrollHeight - el.clientHeight;
        // 無(wú)需滾動(dòng)(這里 - 1因?yàn)閟crollHeight會(huì)四舍五入)
        if (maxScrollTop - 1 <= 0) {
            return;
        }
        // 滾動(dòng)速度
        el.speed = binding.value || 1;
        el.cacheScrollTop = 0;
        el.animationId = RAF(scrollElement.bind(null, el));
        // PS:因?yàn)槲覀兪褂?cacheScrollTop 來(lái)代替 el.scrollTop 處理滾動(dòng)高度,所以這里需要同步一下用戶滾動(dòng)操作后的 scrollTop ==> 而為了保持動(dòng)畫(huà)連貫與流暢,這里千萬(wàn)不要去防抖/節(jié)流!
        el.addEventListener('scroll', elementScrollHandler.bind(null, el));
        // 鼠標(biāo)移入暫停移出繼續(xù)運(yùn)動(dòng)
        if (binding.modifiers.mouse) {
            el.addEventListener('mouseenter', mouseEnterHandler.bind(null, el));
            el.addEventListener('mouseleave', mouseLeaveHandler.bind(null, el));
        }
    },
    unmounted: (el: AnimationElement, binding: DirectiveBinding) => {
        if (binding.modifiers.mouse) {
            el.removeEventListener(
                'mouseenter',
                mouseEnterHandler.bind(null, el)
            );
            el.removeEventListener(
                'mouseleave',
                mouseLeaveHandler.bind(null, el)
            );
        }
    }
};

總結(jié)

  • 使用RAF作為滾動(dòng)動(dòng)畫(huà)“框架”
  • 鼠標(biāo)移入移出動(dòng)畫(huà)暫停/恢復(fù),事件監(jiān)聽(tīng) + cancelAnimationFrame
  • 滾動(dòng)的基礎(chǔ)單位是像素(1px),正常網(wǎng)頁(yè)縮放情況下,會(huì)向下取整,所以得自行管理滾動(dòng)高度,對(duì)其緩存計(jì)算。
  • 網(wǎng)頁(yè)縮放的情況下,滾動(dòng)高度會(huì)減少,同理也通過(guò)緩存計(jì)算來(lái)解決。

敢敢單單,86 行代碼我們就實(shí)現(xiàn)了一個(gè)基本完美的通用列表滾動(dòng)指令。

參考資料: W3C CSSOM View Module

到此這篇關(guān)于Vue3封裝自動(dòng)滾動(dòng)列表指令(含網(wǎng)頁(yè)縮放滾動(dòng)問(wèn)題)的文章就介紹到這了,更多相關(guān)Vue3 自動(dòng)滾動(dòng)列表指令內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue3項(xiàng)目兼容低版本瀏覽器的全面指南

    Vue3項(xiàng)目兼容低版本瀏覽器的全面指南

    隨著 JavaScript 語(yǔ)言的快速演進(jìn),許多現(xiàn)代特性在低版本瀏覽器中無(wú)法運(yùn)行,本文將詳細(xì)介紹如何使Vue3和TypeScript讓項(xiàng)目完美兼容 IE11等低版本瀏覽器,有需要的小伙伴可以參考一下
    2025-04-04
  • Vue3如何實(shí)現(xiàn)超絲滑打字機(jī)效果組件(可復(fù)用、高定制)

    Vue3如何實(shí)現(xiàn)超絲滑打字機(jī)效果組件(可復(fù)用、高定制)

    最近遇到一個(gè)需求就是使用Web網(wǎng)頁(yè)實(shí)現(xiàn)打字機(jī)特效,覺(jué)著還不錯(cuò),這篇文章主要介紹了Vue3如何實(shí)現(xiàn)超絲滑打字機(jī)效果組件的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2026-03-03
  • vue3中的ref和reactive定義數(shù)組方式

    vue3中的ref和reactive定義數(shù)組方式

    這篇文章主要介紹了vue3中的ref和reactive定義數(shù)組方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 淺談vue中使用圖片懶加載vue-lazyload插件詳細(xì)指南

    淺談vue中使用圖片懶加載vue-lazyload插件詳細(xì)指南

    本篇文章主要介紹了淺談vue中使用圖片懶加載vue-lazyload插件詳細(xì)指南,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-10-10
  • vue中動(dòng)態(tài)添加class類名的方法

    vue中動(dòng)態(tài)添加class類名的方法

    今天小編就為大家分享一篇vue中動(dòng)態(tài)添加class類名的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • vue使用原生js創(chuàng)建元素樣式不生效問(wèn)題及解決

    vue使用原生js創(chuàng)建元素樣式不生效問(wèn)題及解決

    這篇文章主要介紹了vue使用原生js創(chuàng)建元素樣式不生效問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • vue項(xiàng)目中vue-echarts講解及常用圖表實(shí)現(xiàn)方案(推薦)

    vue項(xiàng)目中vue-echarts講解及常用圖表實(shí)現(xiàn)方案(推薦)

    這篇文章主要介紹了vue項(xiàng)目中vue-echarts講解及常用圖表方案實(shí)現(xiàn),主要針對(duì)代碼示例中的內(nèi)容進(jìn)行問(wèn)題講解,詳細(xì)代碼在文章中給大家提到,需要的朋友可以參考下
    2022-09-09
  • vue proxyTable 接口跨域請(qǐng)求調(diào)試的示例

    vue proxyTable 接口跨域請(qǐng)求調(diào)試的示例

    本篇文章主要介紹了vue proxyTable 接口跨域請(qǐng)求調(diào)試的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09
  • vue+element-ui+sortable.js實(shí)現(xiàn)表格拖拽功能

    vue+element-ui+sortable.js實(shí)現(xiàn)表格拖拽功能

    這篇文章主要為大家詳細(xì)介紹了vue+element-ui+sortable.js實(shí)現(xiàn)表格拖拽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue組件常用的父子、兄弟、跨組件等傳值方法

    vue組件常用的父子、兄弟、跨組件等傳值方法

    Vue常用的三種傳值方式有:??父?jìng)髯??子傳父??非父子傳值?引用官網(wǎng)的一句話:父子組件的關(guān)系可以總結(jié)為?prop?向下傳遞,事件向上傳遞,熟悉vue各類關(guān)系的組件之間傳值方法會(huì)令開(kāi)發(fā)更加得心應(yīng)手,下面將對(duì)父子、兄弟、頁(yè)級(jí)組件之間的傳值作淺談
    2023-12-12

最新評(píng)論

纳雍县| 咸宁市| 玉溪市| 七台河市| 富蕴县| 宜昌市| 贞丰县| 错那县| 广宗县| 黑山县| 故城县| 嘉峪关市| 河南省| 哈巴河县| 湖南省| 田阳县| 瑞金市| 奈曼旗| 山阳县| 京山县| 长沙市| 静乐县| 鄯善县| 南乐县| 淮北市| 望谟县| 镇赉县| 濮阳市| 班玛县| 阳泉市| 年辖:市辖区| 弋阳县| 临潭县| 浦县| 长沙县| 东乡| 陆川县| 尼玛县| 那坡县| 雷山县| 曲阳县|