关于javascript:实践积累-用Vue3简单写一个单行横向滚动组件

6次阅读

共计 7792 个字符,预计需要花费 20 分钟才能阅读完成。

目录

版权申明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接和本申明。

  • 效果图
  • 需要剖析
  • 实现剖析

    • 款式展现剖析
    • 变量剖析
    • 办法剖析
  • 实现步骤

      1. 实现模板
      1. 实现 css
      1. 首先获取 list
      1. 页面挂载后监听 groupBoxRef 的 scroll 事件并获取以后的滚动地位
      1. 计算展现的宽度显隐箭头,当卡片宽度大于外层宽度就展现
      1. 管制箭头展现方向
      1. 监听外层宽度扭转和窗口大小扭转箭头显隐
  • 残缺代码

效果图

把之前实现的一个效果图摘出来记录一下,外围代码在这里,如果我的项目中用到其余的技术,也很容易改。

需要剖析

  1. 展现数据 始终一行,多余的局部能够出滚动条,同时暗藏滚动条款式。
  2. 反对笔记本 触控滑动 展现
  3. 反对 鼠标点击滑动,多余的时候呈现箭头按钮,默认滑动 3 个卡片的地位,顶头就切换方向
  4. 页面呈现变动的时候要监听 及时显示或暗藏按钮

实现剖析

款式展现剖析

  • 外层管制总体组件宽度

    • 内层箭头区域展现,外部应用 flex 布局,相对定位到右侧

      • 外部箭头 svg 图标,垂直居中
    • 内层管制滚动区域宽度,外部应用 flex 布局,管制一层展现,溢出滚动展现,并暗藏滚动条

      • 外部确定卡片宽高和间距,最初一个左边距为 0

    变量剖析

  • 卡片 list:Array
  • 管制箭头显隐 arrowShow:Boolean,管制箭头方向 direction:String
  • 获取滚动地位 scrollPosition = {left: 0, top: 0}
  • 计算宽度须要的 ref:管制滚动条层 groupBoxRef,卡片 groupCardRef

办法剖析

  1. 获取 list(能够 http,也能够 props,依据需要来定)
  2. 页面挂载之后,监听 groupBoxRef 的 scroll 事件和窗口变动的 resize 事件
  3. 箭头的显隐判断办法,扭转箭头方向的办法
  4. 鼠标点击箭头的办法

实现步骤

1. 实现模板

<template>
  <div class="index-group-box">
    <!-- 左边滑动箭头 -->
    <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">
          名称
          <span>{{item.name}}</span>
        </div>
      </div>
    </div>
  </div>
</template>

2. 实现 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. 页面挂载后监听 groupBoxRef 的 scroll 事件并获取以后的滚动地位

// 增加 reactive 和 onMounted
import {defineComponent, ref, reactive, onMounted} 
...
const groupBoxRef = ref(null); // 获取外层卡⽚ ref
const groupCardRef = ref(null); // 获取卡⽚ ref
const scrollPosition = reactive({
    left: 0,
    top: 0
}); // 滚动地位
...
// 获取 scroll 函数的地位
const handleScroll = e => {
    scrollPosition.left = e.target.scrollLeft;
    scrollPosition.top = e.target.scrollTop;
}

getMyGroup();

onMounted(() => {
    // 监听 scroll 事件
    groupBoxRef.value.addEventListener('scroll', handleScroll, true);
})

return {
    // data
    groupInfo,
    // ref
    groupBoxRef,
    groupCardRef,
};

5. 计算展现的宽度显隐箭头,当卡片宽度大于外层宽度就展现

  • 卡片宽度:groupCardRef.value.offsetWidth
  • 外层宽度:groupBoxRef.value.offsetWidth
  • 滚动区域宽度:卡片数量 *(卡片宽度 + 左边距)- 最初一个左边距
<div class="scrollX" v-if="arrowShow">
    <img src='../assets/arrow-left-bold.svg'/>
</div>
...
const arrowShow = ref(false); // 滚动箭头是否显示

// 获取卡⽚宽度,第⼀个参数是卡⽚个数,默认是整个数组,第⼆个参数是残余的 margin
const getWidth = (num = groupInfo.value.length, restMargin = 16) => {
    // 如果没有内容就返回 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(() => {
    // 监听 scroll 事件
    groupBoxRef.value.addEventListener('scroll', handleScroll, true);
    // 首次查看箭头展现
    checkArrowShow();})

6. 管制箭头展现方向

  • 初始朝右,横向滚动区域为 0 就朝右,残余宽度比外层宽度小就朝左
  • 残余宽度:滚动区域宽度 - 滚动间隔
<!-- 增加点击箭头事件和箭头方向 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'); // 默认项⽬组箭头向右
...
// 扭转滚动⽅向
const changeArrow = (scrollLeft) => {
    // 默认获取 scoll 局部整个宽度
    const getScrollWidth = getWidth();
    // 计算得出残余宽度
    const restWidth = getScrollWidth - scrollLeft
    if (restWidth <= groupBoxRef.value.offsetWidth) {direction.value = 'left'} else if (scrollLeft === 0) {direction.value = 'right'}
}

// ⿏标点击滚动
const groupScroll = async () => {
    // 计算挪动宽度,当初是挪动 3 个卡片的数量
    const getMoveWidth = getWidth(3, 0);
    // 如果方向是左边就 +,右边就 -
    if (direction.value === 'right') {groupBoxRef.value.scrollLeft += getMoveWidth;} else {groupBoxRef.value.scrollLeft -= getMoveWidth;}
    // 滚动须要工夫能力获取最新的间隔,依据新的间隔看箭头的方向
    setTimeout(() => {changeArrow(groupBoxRef.value.scrollLeft);
    }, 500)
}

// 触摸板滑动的时候地位实时扭转箭头方向
const handleScroll = e => {
    ...
    changeArrow(scrollPosition.left);
}

return {
        
    // 新加的 data
    ...
    direction,
    // ref
    ...
    // 新加的 methods
    groupScroll
};

7. 监听外层宽度扭转和窗口大小扭转箭头显隐

import {defineComponent, ref, reactive, onMounted, watchEffect} from 'vue';
...
watchEffect(() => {checkArrowShow();
})

onMounted(() => {
    ...
    // 监听窗⼝变动事件,判断 arrow 的展现
    window.addEventListener('resize', checkArrowShow, true);
})

残缺代码

<template>
    <div class="index-group-box">
        <!-- 左边滑动箭头 -->
        <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">
                    名称
                    <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'); // 默认箭头向右
        const arrowShow = ref(false); // 滚动箭头是否显示
        const groupBoxRef = ref(null); // 获取外层卡⽚ ref
        const groupCardRef = ref(null); // 获取卡⽚ ref
        const scrollPosition = reactive({
            left: 0,
            top: 0
        }); // 滚动地位

  
        // 获取卡片列表
        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;
        }
    
        // 获取卡⽚宽度,第⼀个参数是卡⽚个数,默认是整个数组,第⼆个参数是残余的 margin
        const getWidth = (num = groupInfo.value.length, restMargin = 16) => {
            // 如果没有内容就返回 0
            if(!groupCardRef.value) return 0;
            return num * (groupCardRef.value.offsetWidth + 16) - restMargin;
        }
        // 扭转滚动⽅向
        const changeArrow = (scrollLeft) => {
            // 默认获取 scoll 局部整个宽度
            const getScrollWidth = getWidth();
            // 获取残余宽度
            const restWidth = getScrollWidth - scrollLeft
            if (restWidth <= groupBoxRef.value.offsetWidth) {direction.value = 'left'} else if (scrollLeft === 0) {direction.value = 'right'}
        }
        // ⿏标点击滚动
        const groupScroll = async () => {
            // 获取滚动宽度
            const getMoveWidth = getWidth(3, 0);
            if (direction.value === 'right') {groupBoxRef.value.scrollLeft += getMoveWidth;} else {groupBoxRef.value.scrollLeft -= getMoveWidth;}
            // 滚动须要工夫能力获取最新的间隔
            setTimeout(() => {changeArrow(groupBoxRef.value.scrollLeft);
            }, 500)
        }

        // 判断 arrow 是否展现
        const checkArrowShow = () => {arrowShow.value = getWidth() > groupBoxRef.value?.offsetWidth ? true : false;
        }

        watchEffect(() => {checkArrowShow();
        })

        // 获取 scroll 函数的地位
        const handleScroll = e => {
            scrollPosition.left = e.target.scrollLeft;
            scrollPosition.top = e.target.scrollTop;
            changeArrow(scrollPosition.left);
        }

        getMyGroup();

        onMounted(() => {
            // 监听 scroll 事件
            groupBoxRef.value.addEventListener('scroll', handleScroll, true);
            // 监听窗⼝变动事件,判断 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>
正文完
 0