目錄
版權(quán)聲明:本文為博主原創(chuàng)文章俗慈,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
- 效果圖
- 需求分析
- 實(shí)現(xiàn)分析
- 樣式展示分析
- 變量分析
- 方法分析
- 實(shí)現(xiàn)步驟
- 實(shí)現(xiàn)模板
- 實(shí)現(xiàn)css
- 首先獲取list
- 頁(yè)面掛載后監(jiān)聽(tīng)
groupBoxRef
的scroll
事件并獲取當(dāng)前的滾動(dòng)位置
- 頁(yè)面掛載后監(jiān)聽(tīng)
- 計(jì)算展示的寬度顯隱箭頭,當(dāng)卡片寬度大于外層寬度就展示
- 控制箭頭展示方向
- 監(jiān)聽(tīng)外層寬度改變和窗口大小改變箭頭顯隱
- 完整代碼
效果圖
把之前完成的一個(gè)效果圖摘出來(lái)記錄一下年局,核心代碼在這里,如果項(xiàng)目中用到其他的技術(shù)咸产,也很容易改矢否。
需求分析
- 展示數(shù)據(jù)
始終一行
,多余的部分可以出滾動(dòng)條脑溢,同時(shí)隱藏滾動(dòng)條樣式僵朗。 - 支持筆記本
觸控滑動(dòng)
展示 - 支持
鼠標(biāo)點(diǎn)擊滑動(dòng)
,多余的時(shí)候出現(xiàn)箭頭按鈕,默認(rèn)滑動(dòng)3個(gè)卡片的位置验庙,頂頭就切換方向 - 當(dāng)
頁(yè)面出現(xiàn)變動(dòng)的時(shí)候要監(jiān)聽(tīng)
及時(shí)顯示或隱藏按鈕
實(shí)現(xiàn)分析
樣式展示分析
- 外層控制總體組件寬度
- 內(nèi)層箭頭區(qū)域展示顶吮,內(nèi)部使用flex布局,絕對(duì)定位到右側(cè)
- 內(nèi)部箭頭svg圖標(biāo)壶谒,垂直居中
- 內(nèi)層控制滾動(dòng)區(qū)域?qū)挾仍平茫瑑?nèi)部使用flex布局,控制一層展示汗菜,溢出滾動(dòng)展示,并隱藏滾動(dòng)條
- 內(nèi)部確定卡片寬高和間距挑社,最后一個(gè)右邊距為0
- 內(nèi)層箭頭區(qū)域展示顶吮,內(nèi)部使用flex布局,絕對(duì)定位到右側(cè)
變量分析
- 卡片
list:Array
- 控制箭頭顯隱
arrowShow:Boolean
陨界,控制箭頭方向direction:String
- 獲取滾動(dòng)位置
scrollPosition = {left: 0, top: 0}
- 計(jì)算寬度需要的ref:控制滾動(dòng)條層
groupBoxRef
,卡片groupCardRef
方法分析
- 獲取
list
(可以http
痛阻,也可以props
菌瘪,根據(jù)需求來(lái)定) - 頁(yè)面掛載之后,監(jiān)聽(tīng)
groupBoxRef
的scroll
事件和窗口變化的resize
事件 - 箭頭的顯隱判斷方法阱当,改變箭頭方向的方法
- 鼠標(biāo)點(diǎn)擊箭頭的方法
實(shí)現(xiàn)步驟
1. 實(shí)現(xiàn)模板
<template>
<div class="index-group-box">
<!-- 右邊滑動(dòng)箭頭 -->
<div class="scrollX">
<img src='../assets/arrow-left-bold.svg'/>
</div>
<!-- 卡? -->
<div class="index-group-boxIn" ref="groupBoxRef">
<div
v-for="item in groupInfo"
:key="item.id"
ref="groupCardRef"
class="group-card"
>
<div class="card-name">
名稱(chēng)
<span>{{ item.name }}</span>
</div>
</div>
</div>
</div>
</template>
2. 實(shí)現(xiàn)css
<style scoped>
.index-group-box {
padding-right: 16px;
position: relative;
box-sizing: border-box;
width: 100%;
}
.scrollX {
width: 16px;
position: absolute;
top: 0;
right: 0;
height: 100%;
background-color: #512D6D;
display: flex;
justify-content: center;
align-items: center
}
.scrollX:hover {
cursor: pointer;
background-color: #65447d;
}
.index-group-boxIn {
display: flex;
scroll-behavior: smooth;
white-space: nowrap;
overflow-x: auto;
flex: none;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.index-group-boxIn::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.group-card {
padding: 8px 16px;
box-sizing:border-box;
width: 200px;
height: 100px;
border-radius: 4px;
margin-right: 16px;
flex: none;
background: #71EFA3;
color: #54436B;
}
.group-card span{
color: #54436B;
}
.group-card:hover{
background: #ACFFAD;
}
.group-card:nth-last-of-type(1){
margin-right: 0px;
}
</style>
3. 首先獲取list
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'scroll',
setup() {
const groupInfo = ref([]);
// 獲取卡片列表
const getMyGroup = async () => {
const data = [{
id: 1,
name:'卡片1'
},{
id: 2,
name:'卡片2'
},{
id: 3,
name:'卡片3'
},{
id: 4,
name:'卡片4'
},{
id: 5,
name:'卡片5'
}]
groupInfo.value = data;
}
getMyGroup();
return {
// data
groupInfo,
};
},
});
</script>
4. 頁(yè)面掛載后監(jiān)聽(tīng)groupBoxRef的scroll事件并獲取當(dāng)前的滾動(dòng)位置
// 添加reactive和onMounted
import { defineComponent, ref, reactive, onMounted }
...
const groupBoxRef = ref(null); // 獲取外層卡?ref
const groupCardRef = ref(null); // 獲取卡?ref
const scrollPosition = reactive({
left: 0,
top: 0
}); // 滾動(dòng)位置
...
// 獲取scroll函數(shù)的位置
const handleScroll = e => {
scrollPosition.left = e.target.scrollLeft;
scrollPosition.top = e.target.scrollTop;
}
getMyGroup();
onMounted(() => {
// 監(jiān)聽(tīng)scroll事件
groupBoxRef.value.addEventListener('scroll', handleScroll, true);
})
return {
// data
groupInfo,
// ref
groupBoxRef,
groupCardRef,
};
5. 計(jì)算展示的寬度顯隱箭頭俏扩,當(dāng)卡片寬度大于外層寬度就展示
- 卡片寬度:
groupCardRef.value.offsetWidth
- 外層寬度:
groupBoxRef.value.offsetWidth
- 滾動(dòng)區(qū)域?qū)挾龋?code>卡片數(shù)量 * (卡片寬度 + 右邊距)- 最后一個(gè)右邊距
<div class="scrollX" v-if="arrowShow">
<img src='../assets/arrow-left-bold.svg'/>
</div>
...
const arrowShow = ref(false); // 滾動(dòng)箭頭是否顯示
// 獲取卡?寬度,第?個(gè)參數(shù)是卡?個(gè)數(shù)弊添,默認(rèn)是整個(gè)數(shù)組录淡,第?個(gè)參數(shù)是剩余的margin
const getWidth = (num = groupInfo.value.length, restMargin = 16) => {
// 如果沒(méi)有內(nèi)容就返回0
if(!groupCardRef.value) return 0;
return num * (groupCardRef.value.offsetWidth + 16) - restMargin;
}
// 判斷arrow是否展示
const checkArrowShow = () => {
arrowShow.value = getWidth() > groupBoxRef.value?.offsetWidth ? true : false;
}
...
onMounted(() => {
// 監(jiān)聽(tīng)scroll事件
groupBoxRef.value.addEventListener('scroll', handleScroll, true);
// 首次檢查箭頭展示
checkArrowShow();
})
6. 控制箭頭展示方向
- 初始朝右,
橫向滾動(dòng)區(qū)域?yàn)?就朝右油坝,剩余寬度比外層寬度小就朝左
- 剩余寬度:
滾動(dòng)區(qū)域?qū)挾?- 滾動(dòng)距離
<!-- 添加點(diǎn)擊箭頭事件和箭頭方向svg -->
<div class="scrollX" @click="groupScroll" v-if="arrowShow">
<img v-if="direction === 'left'" src='../assets/arrow-left-bold.svg'/>
<img v-else src='../assets/arrow-right-bold.svg'/>
</div>
...
const direction = ref('right'); // 默認(rèn)項(xiàng)?組箭頭向右
...
// 改變滾動(dòng)?向
const changeArrow = (scrollLeft) => {
// 默認(rèn)獲取scoll部分整個(gè)寬度
const getScrollWidth = getWidth();
// 計(jì)算得出剩余寬度
const restWidth = getScrollWidth - scrollLeft
if (restWidth <= groupBoxRef.value.offsetWidth) {
direction.value = 'left'
} else if ( scrollLeft === 0 ) {
direction.value = 'right'
}
}
// ?標(biāo)點(diǎn)擊滾動(dòng)
const groupScroll = async () => {
// 計(jì)算移動(dòng)寬度嫉戚,現(xiàn)在是移動(dòng)3個(gè)卡片的數(shù)量
const getMoveWidth = getWidth(3, 0);
// 如果方向是右邊就+,左邊就-
if (direction.value === 'right') {
groupBoxRef.value.scrollLeft += getMoveWidth;
} else {
groupBoxRef.value.scrollLeft -= getMoveWidth;
}
// 滾動(dòng)需要時(shí)間才能獲取最新的距離澈圈,根據(jù)新的距離看箭頭的方向
setTimeout(() => {
changeArrow(groupBoxRef.value.scrollLeft);
}, 500)
}
// 觸摸板滑動(dòng)的時(shí)候位置實(shí)時(shí)改變箭頭方向
const handleScroll = e => {
...
changeArrow(scrollPosition.left);
}
return {
// 新加的data
...
direction,
// ref
...
// 新加的methods
groupScroll
};
7. 監(jiān)聽(tīng)外層寬度改變和窗口大小改變箭頭顯隱
import { defineComponent, ref, reactive, onMounted, watchEffect } from 'vue';
...
watchEffect(() => {
checkArrowShow();
})
onMounted(() => {
...
// 監(jiān)聽(tīng)窗?變化事件彬檀,判斷arrow的展示
window.addEventListener('resize', checkArrowShow, true);
})
完整代碼
<template>
<div class="index-group-box">
<!-- 右邊滑動(dòng)箭頭 -->
<div class="scrollX" @click="groupScroll" v-if="arrowShow">
<img v-if="direction === 'left'" src='../assets/arrow-left-bold.svg'/>
<img v-else src='../assets/arrow-right-bold.svg'/>
</div>
<!-- 卡? -->
<div class="index-group-boxIn" ref="groupBoxRef">
<div
v-for="item in groupInfo"
:key="item.id"
ref="groupCardRef"
class="group-card"
>
<div class="card-name">
名稱(chēng)
<span>{{ item.name }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import { defineComponent, ref, reactive, onMounted, watchEffect } from 'vue';
export default defineComponent({
name: 'scroll',
setup() {
const groupInfo = ref([]); // 卡片list
const direction = ref('right'); // 默認(rèn)箭頭向右
const arrowShow = ref(false); // 滾動(dòng)箭頭是否顯示
const groupBoxRef = ref(null); // 獲取外層卡?ref
const groupCardRef = ref(null); // 獲取卡?ref
const scrollPosition = reactive({
left: 0,
top: 0
}); // 滾動(dòng)位置
// 獲取卡片列表
const getMyGroup = async () => {
const data = [{
id: 1,
name:'卡片1'
},{
id: 2,
name:'卡片2'
},{
id: 3,
name:'卡片3'
},{
id: 4,
name:'卡片4'
},{
id: 5,
name:'卡片5'
}]
groupInfo.value = data;
}
// 獲取卡?寬度,第?個(gè)參數(shù)是卡?個(gè)數(shù)瞬女,默認(rèn)是整個(gè)數(shù)組窍帝,第?個(gè)參數(shù)是剩余的margin
const getWidth = (num = groupInfo.value.length, restMargin = 16) => {
// 如果沒(méi)有內(nèi)容就返回0
if(!groupCardRef.value) return 0;
return num * (groupCardRef.value.offsetWidth + 16) - restMargin;
}
// 改變滾動(dòng)?向
const changeArrow = (scrollLeft) => {
// 默認(rèn)獲取scoll部分整個(gè)寬度
const getScrollWidth = getWidth();
// 獲取剩余寬度
const restWidth = getScrollWidth - scrollLeft
if (restWidth <= groupBoxRef.value.offsetWidth) {
direction.value = 'left'
} else if ( scrollLeft === 0 ) {
direction.value = 'right'
}
}
// ?標(biāo)點(diǎn)擊滾動(dòng)
const groupScroll = async () => {
// 獲取滾動(dòng)寬度
const getMoveWidth = getWidth(3, 0);
if (direction.value === 'right') {
groupBoxRef.value.scrollLeft += getMoveWidth;
} else {
groupBoxRef.value.scrollLeft -= getMoveWidth;
}
// 滾動(dòng)需要時(shí)間才能獲取最新的距離
setTimeout(() => {
changeArrow(groupBoxRef.value.scrollLeft);
}, 500)
}
// 判斷arrow是否展示
const checkArrowShow = () => {
arrowShow.value = getWidth() > groupBoxRef.value?.offsetWidth ? true : false;
}
watchEffect(() => {
checkArrowShow();
})
// 獲取scroll函數(shù)的位置
const handleScroll = e => {
scrollPosition.left = e.target.scrollLeft;
scrollPosition.top = e.target.scrollTop;
changeArrow(scrollPosition.left);
}
getMyGroup();
onMounted(() => {
// 監(jiān)聽(tīng)scroll事件
groupBoxRef.value.addEventListener('scroll', handleScroll, true);
// 監(jiān)聽(tīng)窗?變化事件,判斷arrow的展示
window.addEventListener('resize', checkArrowShow, true);
// 首次檢查箭頭展示
checkArrowShow();
})
return {
// data
groupInfo,
direction,
arrowShow,
// ref
groupBoxRef,
groupCardRef,
// methods
groupScroll
};
},
});
</script>
<style scoped>
.index-group-box {
padding-right: 16px;
position: relative;
box-sizing: border-box;
width: 100%;
}
.scrollX {
width: 16px;
position: absolute;
top: 0;
right: 0;
height: 100%;
background-color: #512D6D;
display: flex;
justify-content: center;
align-items: center
}
.scrollX:hover {
cursor: pointer;
background-color: #65447d;
}
.index-group-boxIn {
display: flex;
scroll-behavior: smooth;
white-space: nowrap;
overflow-x: auto;
flex: none;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.index-group-boxIn::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.group-card {
padding: 8px 16px;
box-sizing:border-box;
width: 200px;
height: 100px;
border-radius: 4px;
margin-right: 16px;
flex: none;
background: #71EFA3;
color: #54436B;
}
.group-card span{
color: #54436B;
}
.group-card:hover{
background: #ACFFAD;
}
.group-card:nth-last-of-type(1){
margin-right: 0px;
}
</style>