Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
言
最近的一個(gè)項(xiàng)目做的是vue組件中的一個(gè)應(yīng)用,「處理滾動(dòng)列表」,這個(gè)應(yīng)該是很常見(jiàn)的需求了,在項(xiàng)目中遇到的痛點(diǎn),難點(diǎn),如何一步步解決的,以及小細(xì)節(jié)一些優(yōu)化。
借鑒某課的思路,仿QQ音樂(lè)效果,記錄一下,自己字母解決這個(gè)難題,分享給你們,「希望對(duì)你們做移動(dòng)端滾動(dòng)列表問(wèn)題有所幫助」
GitHub倉(cāng)庫(kù)
處理滾動(dòng)列表最終效果
從最終效果來(lái)看,實(shí)現(xiàn)了三個(gè)我的難點(diǎn)
接下來(lái)就是一步步去實(shí)現(xiàn),優(yōu)化上面的效果
better-scroll 移動(dòng)端滾動(dòng)的解決方案
vue-lazyload 圖片懶加載
基本上實(shí)現(xiàn)上面的效果就是基于這兩個(gè)第三方庫(kù)
「better-scroll基本使用」
經(jīng)常會(huì)遇到的問(wèn)題就是初始化了,「還是不能滾動(dòng)」。那么對(duì)于這個(gè)而言,我最近用到一些經(jīng)驗(yàn)是什么呢?
我們先看常見(jiàn)的html結(jié)果
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
...
</ul>
<!-- you can put some other DOMs here, it won't affect the scrolling
</div>
復(fù)制代碼
滾動(dòng)的原理是什么
better-scroll原理說(shuō)明
wrapper是父容器,它一定要有「固定高度」,content是內(nèi)容區(qū)域,它是父元素的第一個(gè)元素,它c(diǎn)ontent會(huì)隨著內(nèi)容的大小撐開(kāi)而撐高,只有這個(gè)高度大于wrapper父容器高度時(shí),才會(huì)出現(xiàn)滾動(dòng),也就是它的原理。
那么我們?cè)趺慈コ跏蓟?/p>
import BScroll from '@better-scroll/core'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper,{})
//{}配置一些信息
復(fù)制代碼
點(diǎn)這里有文檔
接下來(lái)就開(kāi)始把
這個(gè)scroll組件是子組件,也可以算是個(gè)base組件,完成日常滾動(dòng)的效果
<template>
<div ref="wrapper">
<slot></slot>
</div>
</template>
<script type="text/ecmascript-6">
import BScroll from 'better-scroll'
export default {
props: {
probeType: {
type: Number,
default: 1
},
click: {
type: Boolean,
default: true
},
listenScroll: {
type: Boolean,
default: false
},
data: {
type: Array,
default: null
},
pullup: {
type: Boolean,
default: false
},
beforeScroll: {
type: Boolean,
default: false
},
refreshDelay: {
type: Number,
default: 20
}
},
mounted() {
setTimeout(() => {
this._initScroll()
}, 20)
},
methods: {
// 初始化Scroll
_initScroll() {
// 判斷是否初始化
if (!this.$refs.wrapper) {
return
}
// 調(diào)用Scroll實(shí)例,表現(xiàn)可以滑動(dòng)
this.scroll = new BScroll(this.$refs.wrapper, {
probeType: this.probeType,
click: this.click
})
if (this.listenScroll) {
let me = this
this.scroll.on('scroll', (pos) => {
me.$emit('scroll', pos)
})
}
if (this.pullup) {
this.scroll.on('scrollEnd', () => {
if (this.scroll.y <= (this.scroll.maxScrollY + 50)) {
this.$emit('scrollToEnd')
}
})
}
if (this.beforeScroll) {
this.scroll.on('beforeScrollStart', () => {
this.$emit('beforeScroll')
})
}
},
disable() {
this.scroll && this.scroll.disable()
},
enable() {
this.scroll && this.scroll.enable()
},
refresh() { // 刷新scroll,重新計(jì)算高度
this.scroll && this.scroll.refresh()
},
scrollTo() {
this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
},
scrollToElement() {
this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
}
},
watch: {
// 監(jiān)聽(tīng)到數(shù)據(jù)的變化,就會(huì)重新去refresh數(shù)據(jù),重新去計(jì)算響應(yīng)的數(shù)據(jù)
data() {
setTimeout(() => {
this.refresh()
}, this.refreshDelay)
}
}
}
</script>
<style scoped lang="stylus" rel="stylesheet/stylus">
</style>
復(fù)制代碼
在父組件中導(dǎo)入即可
listview組件導(dǎo)入
<template>
<scroll
:listen-scroll="listenScroll"
:probe-type="probeType"
:data="data"
class="listview"
ref="listview"
>
<ul>
<li v-for="(group,index) in data" class="list-group" ref="listGroup" :key="index">
<h2 class="list-group-title">{{group.title}}</h2>
<uL>
<li
@click="selectItem(item)"
v-for="(item, index) in group.items"
class="list-group-item"
:key="index"
>
<img class="avatar" v-lazy="item.avatar" />
<span class="name">{{item.name}}</span>
</li>
</uL>
</li>
</ul>
</scroll>
</template>
復(fù)制代碼
然后導(dǎo)入scroll組件即可,看看效果
處理滾動(dòng)列表-實(shí)現(xiàn)列表滾動(dòng)
上面在listview組件中導(dǎo)入scroll組件,完成基本的列表滾動(dòng)效果,接下來(lái),完善一步一步效果吧。
<div
class="list-shortcut"
@touchstart="onShortcutTouchStart"
@touchmove.stop.prevent="onShortcutTouchMove"
>
<!-- data-index方便獲取一個(gè)列表中的index -->
<ul>
<li
v-for="(item, index) in shortcutList"
:data-index="index"
class="item"
:class="{'current':currentIndex===index}"
:key="index"
>{{item}}</li>
</ul>
</div>
復(fù)制代碼
點(diǎn)擊右側(cè)快速路口的話,會(huì)跳轉(zhuǎn)到相應(yīng)的title去,使用的方法就是
scrollElement
scrollToElement(el, time, offsetX, offsetY, easing)
「這個(gè)方法很方便的解決了我們第一個(gè)難點(diǎn)」,現(xiàn)在就差獲取右側(cè)快速路口的索引值了
給每一個(gè)li增加一個(gè)data-index屬性名稱(chēng),值為index下
:data-index="index"
復(fù)制代碼
這樣子每次就可以獲取當(dāng)前的索引值
有了索引值,我們就可以直接調(diào)用srcollToElement()方法,完成左側(cè)的跳轉(zhuǎn)效果。
this.$refs.listview.scrollToElement(this.$refs.listGroup[index], 0);
// 這個(gè)index就是獲取到下標(biāo)索引值,然后通過(guò)這個(gè)
// 這個(gè)第二個(gè)參數(shù)是滾動(dòng)的動(dòng)畫(huà)的時(shí)間,我們默認(rèn)為0就行,文檔上面也有專(zhuān)門(mén)的說(shuō)明,可以去看看。
復(fù)制代碼
我們看看效果吧下
處理滾動(dòng)列表-實(shí)現(xiàn)點(diǎn)擊右側(cè)跳轉(zhuǎn)相應(yīng)位置
接下來(lái)完成「touchMove事件」,我們綁定到div上
@touchmove.stop.prevent="onShortcutTouchMove"
// 兩個(gè)修飾符阻止冒泡以及默認(rèn)的事件
復(fù)制代碼
思路
看代碼
onShortcutTouchStart(e) {
// 獲取到右側(cè)的列表索引值
let anchorIndex = getData(e.target, "index");
let firstTouch = e.touches[0];
this.touch.y1 = firstTouch.pageY; // 計(jì)入一開(kāi)始y軸上的位置
this.touch.anchorIndex = anchorIndex; // 保存了每次點(diǎn)擊的錨點(diǎn)
this._scrollTo(anchorIndex);
},
// 監(jiān)聽(tīng)的是TouchMove事件
onShortcutTouchMove(e) {
let firstTouch = e.touches[0];
this.touch.y2 = firstTouch.pageY;
// 滾動(dòng)的兩個(gè)差值 也就是y軸上的偏移
// 除以每個(gè)高度,這樣子的話,就知道偏移了幾個(gè)錨點(diǎn)
let delta = ((this.touch.y2 - this.touch.y1) / ANCHOR_HEIGHT) | 0;
let anchorIndex = parseInt(this.touch.anchorIndex) + delta;
this._scrollTo(anchorIndex);
},
復(fù)制代碼
效果怎么樣呢,基本上點(diǎn)擊和手勢(shì)移動(dòng)都較為完美的實(shí)現(xiàn)了。
處理滾動(dòng)列表-實(shí)現(xiàn)手勢(shì)移動(dòng)右側(cè)跳轉(zhuǎn)相應(yīng)位置
左右聯(lián)動(dòng)的效果指的是左側(cè)點(diǎn)擊到某個(gè)區(qū)域,緊接著右側(cè)快速路口也跳轉(zhuǎn)到相應(yīng)位置,這里其實(shí)指的就是高亮效果。
效果就是滑動(dòng)列表,右側(cè)的字母會(huì)相應(yīng)的高亮,達(dá)到同步的作用,難點(diǎn)是什么呢?
ListGroup計(jì)算高度
從圖片上面看,我們發(fā)現(xiàn)每個(gè)listGroup分組里面的成員是不固定的,所以我們?cè)趺慈カ@取到相應(yīng)的currentIndex呢?
「我們可以獲取到每次滾動(dòng)的距離,那怎么樣去獲取相應(yīng)的currentIndex呢,比如滑到K分組時(shí),currentIndex是對(duì)應(yīng)的下標(biāo)?」
有個(gè)不錯(cuò)的思路:
那么我們按照上面的思路來(lái)完善吧
_calculateHeight() {
// 這個(gè)方法就是計(jì)算每個(gè)listGroup高度
this.listHeight = [];
const list = this.$refs.listGroup;
let height = 0;
this.listHeight.push(height);
for (let i = 0; i < list.length; i++) {
let item = list[i];
height += item.clientHeight;
this.listHeight.push(height);
}
},
復(fù)制代碼
這個(gè)listHeigth數(shù)據(jù)就是我們維護(hù)的第i個(gè)分組的clientHeight距離
第二步,我們監(jiān)控這個(gè)scrollY,這個(gè)變量表示的就是滾動(dòng)的距離
watch: {
// 每次去watch這個(gè)滾動(dòng)的距離,
scrollY(newY) {
const listHeight = this.listHeight;
// 當(dāng)滾動(dòng)到頂部,newY>0
if (newY > 0) {
this.currentIndex = 0;
return;
}
// 在中間部分滾動(dòng)
for (let i = 0; i < listHeight.length - 1; i++) {
let height1 = listHeight[i];
let height2 = listHeight[i + 1];
if (-newY >= height1 && -newY < height2) {
this.currentIndex = i;
this.diff = height2 + newY;
return;
}
}
// 當(dāng)滾動(dòng)到底部,且-newY大于最后一個(gè)元素的上限
this.currentIndex = listHeight.length - 2;
},
復(fù)制代碼
這里需要提醒的就是,我們?cè)趺慈ツ玫竭@個(gè)scrollY滾動(dòng)距離呢?
說(shuō)到這個(gè),我們得看到scroll組件中,閱讀它的API,會(huì)發(fā)現(xiàn)它提供了on方法,該方法可以去監(jiān)聽(tīng)該「實(shí)例的鉤子函數(shù)」,所以我們?nèi)?strong>「監(jiān)聽(tīng)鉤子函數(shù)scroll」
「scroll鉤子函數(shù)」
所以我們可以通過(guò)這個(gè)鉤子來(lái)獲取滾動(dòng)的實(shí)時(shí)坐標(biāo)
if (this.listenScroll) {
let me = this
this.scroll.on('scroll', (pos) => {
// me指的就是實(shí)例
// 通過(guò)監(jiān)聽(tīng)scroll事件,有一個(gè)回調(diào),pos是一個(gè)對(duì)象,有x,y軸的具體距離
// 去派發(fā)一個(gè)scroll事件,這樣子外部也就是父組件可以拿到我們的pos
me.$emit('scroll', pos)
})
}
復(fù)制代碼
這樣子我們?cè)谧咏M件scroll中向外派發(fā)一個(gè)scroll事件,并且把「pos = {Object} {x, y} 滾動(dòng)的實(shí)時(shí)坐標(biāo)」向外傳遞,這樣子的話,父組件通過(guò)@scroll="scroll" 就可以拿到這個(gè)坐標(biāo)pos
這樣子我們這個(gè)難點(diǎn)就解決了。
我們來(lái)看看效果
這樣子基本上問(wèn)題就解決了,但是呢還會(huì)遇到一個(gè)問(wèn)題?
這個(gè)是文檔上面的內(nèi)容,我們可以看到這個(gè)配置項(xiàng)還是很重要的,我們listview組件需要通過(guò)props向子組件傳遞probeType值,值為3,這樣子就可以「在滾動(dòng)中實(shí)時(shí)派發(fā) scroll 事件」。
<scroll :probe-type='3'></scroll>
// 當(dāng)然了,這個(gè)probeTyep會(huì)在data中拿到
復(fù)制代碼
咱給小編:
1. 點(diǎn)贊+評(píng)論
2. 點(diǎn)頭像關(guān)注,轉(zhuǎn)發(fā)給有需要的朋友。
您的支持是小編不斷輸出的動(dòng)力,謝謝!!
CSS 和 JS 的原生平滑滾動(dòng)
你想要一個(gè)平滑的滾動(dòng)嗎? 忘記 JQuery,我們已經(jīng)過(guò)去了。 讓我向您介紹我們的原生平滑滾動(dòng)工具。
CSS 滾動(dòng)行為
CSS scroll-behavior 屬性接受三個(gè)值之一 - 實(shí)際上是兩個(gè)值,因?yàn)槠渲幸粋€(gè)已被棄用。
我說(shuō)“以編程方式觸發(fā)”是因?yàn)樗粫?huì)平滑滾動(dòng)鼠標(biāo)滾輪。
以編程方式觸發(fā)滾動(dòng)事件的一些方法是:
- Window.scrollTo()
- Window.scrollBy()
- Element.scrollTo()
- Element.scrollBy()
- Element.scrollIntoView()
- Element.scrollLeft = x
- Element.scrollTop = y
我們將分別探索這些方法。
(注)Window.scroll() 和 Element.scroll()
也許你已經(jīng)注意到我沒(méi)有提到 scroll() 方法。
這是因?yàn)?Window.scroll() 和 Element.scroll() 實(shí)際上是與 Window.scrollTo() 和 Element.scrollTo() 相同的方法。為避免重復(fù)內(nèi)容,我將僅參考 scrollTo()。在實(shí)踐中,您可以使用任何一種,只需選擇一個(gè)并保持一致。
Window.scrollTo() 和 Element.scrollTo()
此方法非常適合滾動(dòng)到絕對(duì)坐標(biāo)。如果您有要將用戶(hù)滾動(dòng)到的位置的 x 和 y 坐標(biāo),您可以簡(jiǎn)單地調(diào)用 window.scrollTo(x, y) ,它會(huì)尊重頁(yè)面的 CSS 滾動(dòng)行為。
這同樣適用于可滾動(dòng)元素。您只需調(diào)用 element.scrollTo(x, y) ,它就會(huì)尊重元素的 CSS 滾動(dòng)行為。
這個(gè)方法還有一個(gè)新的簽名,它使用一個(gè)對(duì)象而不是兩個(gè)數(shù)字參數(shù),通過(guò)這個(gè)新的簽名,我們可以顯式地設(shè)置我們的滾動(dòng)行為。
// For window
window.scrollTo({
left: x,
top: y,
behavior: 'smooth'
});
// For element
const el = document.querySelector(...);
el.scrollTo({
left: x,
top: y,
behavior: 'smooth'
});
Element.scrollLeft 和 Element.scrollTop
設(shè)置元素 .scrollLeft 和 .scrollTop 屬性與使用坐標(biāo)調(diào)用 Element.scrollTo() 相同。 它將尊重元素的 CSS 滾動(dòng)行為。
const el = document.querySelector(...);
const x = 100;
const y = 500;
// Setting .scrollLeft and .scrollTop with smooth scroll
el.style.scrollBehavior = 'smooth';
el.scrollLeft = x;
el.scrollTop = y;
// Is the same as calling Element.scrollTo()
el.scrollTo({ left: x, top: y, behavior: 'smooth' });
(注)負(fù)元素.scrollLeft
如果你的元素文本的方向是 rtl,scrollLeft = 0 表示水平滾動(dòng)的最右邊位置,并且隨著你向左移動(dòng),值會(huì)減小。
對(duì)于寬度為 100px,可滾動(dòng)寬度為 500px,方向?yàn)?rtl 的可滾動(dòng)元素,最左邊的位置是 scrollLeft = -400。
<div id="scrollable" style="width: 100px; overflow: auto" dir="rtl">
<div style="width: 500px; height: 100px; background: green"></div>
</div>
<p id="output"></p>
const scrollable = document.querySelector('#scrollable');
const output = document.querySelector('#output');
const updateOutput = () => {
output.textContent = `scrollLeft: ${scrollable.scrollLeft}`;
};
updateOutput();
scrollable.addEventListener('scroll', updateOutput);
Window.scrollBy() 和 Element.scrollBy()
此方法與 Window.scrollTo() 或 Element.scrollTo() 具有完全相同的簽名。 它接受 x 和 y 作為兩個(gè)數(shù)字參數(shù)或作為具有可選 left、top 和 behavior 屬性的對(duì)象的單個(gè)參數(shù)。
這里的區(qū)別是我們不是傳遞絕對(duì)坐標(biāo),而是相對(duì)值。 如果我們 scrollBy({ top: 10 }),我們從當(dāng)前位置向下滾動(dòng) 10 個(gè)像素,而不是從頁(yè)面開(kāi)頭向下滾動(dòng) 10 個(gè)像素。
// For window
window.scrollBy({ top: 10 }); // Scroll 10px down
window.scrollBy({ left: 20 }); // Then 20px to the right
window.scrollBy({ top: 50 }); // And then 50px down
// For element
const el = document.querySelector(...);
el.scrollBy({ top: 10 }); // Scroll 10px down
el.scrollBy({ left: 20 }); // Then 20px to the right
el.scrollBy({ top: 50 }); // And then 50px down
(注) Window.scrollByLines() 和 Window.scrollByPages()
Firefox 更進(jìn)一步,實(shí)現(xiàn)了滾動(dòng)多行或多頁(yè)的方法。 這是一個(gè)僅適用于 Firefox 的非標(biāo)準(zhǔn)功能,因此您可能不想在生產(chǎn)中使用它。
通過(guò)將 100vh(用于頁(yè)面)和 1rem(用于行)轉(zhuǎn)換為像素并將該值傳遞給 Window.scrollBy(),您可以在所有主要瀏覽器中實(shí)現(xiàn)類(lèi)似的效果。
const toPixels = require('to-px'); // From NPM
const page = toPixels('100vh');
window.scrollBy(0, page); // window.scrolByPages(1)
const line = toPixels('1rem');
window.scrollBy(0, line); // window.scrolByLines(1)
Element.scrollIntoView()
但大多數(shù)時(shí)候,我們并不關(guān)心任何硬編碼的坐標(biāo),我們只想將用戶(hù)滾動(dòng)到屏幕上的特定元素。 這可以通過(guò) Element.scrollIntoView() 輕松(更明確地)完成。
可以不帶參數(shù)調(diào)用此方法,它將滾動(dòng)頁(yè)面(尊重 CSS 滾動(dòng)行為)直到元素在頂部對(duì)齊(除非元素在頁(yè)面底部,在這種情況下,它會(huì) 盡可能滾動(dòng))。
<div style="height: 2000px">Some space</div>
<div id="target" style="background: green">Our element</div>
<div style="height: 500px">More space</div>
const target = document.querySelector('#target');
target.scrollIntoView();
您可以通過(guò)傳遞布爾值或?qū)ο髞?lái)進(jìn)一步自定義元素在視圖中的位置。
const el = document.querySelector(...);
// Default, aligns at the top
el.scrollIntoView(true);
// Aligns at the bottom
el.scrollIntoView(false);
// Aligns vertically at the center
el.scrollIntoView({ block: 'center' });
// Vertical top and horizontal center
el.scrollIntoView({ block: 'start', inline: 'center' });
// Vertical bottom and horizontal right
el.scrollIntoView({ block: 'end', inline: 'end' });
// Vertical center and smooth
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
// Vertical nearest
el.scrollIntoView({ block: 'nearest' });
當(dāng)使用一個(gè)對(duì)象來(lái)定義元素的位置時(shí),注意 block 是指垂直放置,而 inline 是指水平放置。此外,“最近”的位置可以是頂部/左側(cè)或底部/右側(cè),以最近的為準(zhǔn),如果元素已經(jīng)在視圖中,它也可以是空的。
瀏覽器支持
{關(guān)于瀏覽器支持} 在撰寫(xiě)本文時(shí),所有主流瀏覽器(Safari 除外)都支持平滑滾動(dòng)和本文中描述的滾動(dòng)方法。
如果你需要更多的保證,我在我的項(xiàng)目中使用了一個(gè)非常好的平滑滾動(dòng) polyfill。它填充了 scroll()、scrollTo()、scrollBy()、scrollIntoView() 和 CSS 滾動(dòng)行為。它不支持通過(guò)設(shè)置 scrollLeft/scrollTop 來(lái)平滑滾動(dòng),也不支持 scrollIntoView() 選項(xiàng)(它總是在頂部對(duì)齊元素)。
如果你有更多的好奇心,我強(qiáng)烈建議你研究一下 polyfill 的源代碼,總共不到 500 行。我做了一個(gè) PR 來(lái)稍微改進(jìn)文檔,你可能會(huì)發(fā)現(xiàn)代碼更容易理解。
祝你有美好的一天,很快見(jiàn)到你。
extarea內(nèi)容某個(gè)高度之內(nèi)自適應(yīng),超過(guò)時(shí)候指定高度固定然后出現(xiàn)滾動(dòng)條!
效果如下:當(dāng)你輸入超過(guò)設(shè)置的高度后,就會(huì)固定此高度不變。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。