移动端H5封装一个 ScrollList 横向滚动列表组件,实现向左滑动

效果:

1.封装组件:

vue 复制代码
<template>
  <div class="scroll-list">
    <div
      class="scroll-list-content"
      :style="{ background, color, fontSize: size }"
      ref="scrollListContent"
      >
      <div class="scroll-list-group" v-for="(item, index) in list" :key="index">
        <div class="scroll-list-item" v-for="menuItem in item" :style="getItemStyle">
          <img class="scroll-list-item-img"  :style=iconSize alt="" v-lazy="menuItem[icon]"
            @click="navigateToRoute(menuItem.path)"/>
            <!--<van-image-->
            <!--		lazy-load-->
            <!--		fit="cover"-->
            <!--		class="scroll-list-item-img" :src="menuItem[icon]" :style=iconSize alt=""-->
            <!--		@click="navigateToRoute(menuItem.path)"-->
            <!--/>-->
            <span class="scroll-list-item-text" :style="menuItemName[0]">{{ menuItem[name] }}</span>
          </div>
      </div>
    </div>
    <div v-if="isScrollBar && indicator" class="scroll-list-indicator">
      <div
        class="scroll-list-indicator-bar"
        :style="{ width: width }"
        ref="scrollListIndicatorBar"
        >
        <span
          :style="{ height: height }"
          class="scroll-list-indicator-slider"
          ></span>
      </div>
    </div>
  </div>
</template>

<script>

  import {
    defineComponent,
    onMounted,
    onBeforeUnmount,
    reactive,
    ref,
    toRefs,
    nextTick,
    watch,
  } from "vue";
  import {useRouter} from "vue-router";

  export default defineComponent({
    props: {
      list: {
        type: Array,
        default: () => [], // 数据
      },
      indicator: {
        type: Boolean,
        default: true, // 是否显示面板指示器
      },
      indicatorColor: {
        type: String,
        default: "rgba(250,250,250,0.56)", // 指示器非激活颜色  (暂不支持)
      },
      indicatorActiveColor: {
        type: String,
        default: "rgba(250,250,250,0.56)", // 指示器滑块颜色  (暂不支持)
      },
      indicatorWidth: {
        type: String,
        default: "", // 指示器 宽度
      },
      indicatorBottom: {
        type: String,
        default: "", // 指示器 距离内容底部的距离 (设置 0 会隐藏指示器)
      },
      background: {
        type: String,
        default: "", // 内容区域背景色
      },
      color: {
        type: String,
        default: "", // 内容区域文本颜色
      },
      size: {
        type: String,
        default: "", // 内容区域文本字体大小
      },
      indicatorStyle: {
        type: String,
        default: "", // 指示器 样式 (暂不支持)
      },
      icon: {
        type: String,
        default: "icon", // 图标字段
      },
      name: {
        type: String,
        default: "name", // 文本字段
      },
      iconSize: {
        type: Object,
        default:
          {
            width:"40px", height:"40px"
          }
      }, // 设置默认的 icon 大小

      iconNum: {
        type: String,
        default: "4", // 设置默认的 icon 数量
      },

      menuItemName: {
        type: Array,
        //			font-size: 12px;
        //color: #6B5B50;
        default: () => [
          {
            fontSize: "12px",
            color: "#6B5B50",
          }
        ],
        // 设置默认的 icon 数量
      }
    },
    computed: {
      getItemStyle() {
        const widthPercent = 100 / this.iconNum;
        return {
          width: `${widthPercent}%`,
        };
      },

      getNameStyle() {
        return {
          // 在这里添加样式属性,根据 menuItemName 的值来设置
          fontSize: this.menuItemName[0].size,
          color: this.menuItemName[0].color,
          //margin-top: this.menuItemName[0].top;

        }
      }	,
    },
    setup(props) {
      const router = useRouter(); // 获取 Vue Router 的实例

      const width = ref("");
      const height = ref("");
      const {indicatorWidth, indicatorBottom, indicator} = props;
      watch(
      () => [indicatorWidth, indicatorBottom],
      (newVal) => {
      //console.log(newVal);
      const _width = newVal[0].includes("px") ? newVal[0] : newVal[0] + "px";
      const _height = newVal[1].includes("px") ? newVal[1] : newVal[1] + "px";
      width.value = _width;
      height.value = _height;
    },
      {immediate: true}
      );

      const state = reactive({
      scrollListContent: null, // 内容区域 dom
      scrollListIndicatorBar: null, // 底部指示器 dom
      isScrollBar: false,
    });

      onMounted(() => {
      nextTick(() => {
      state.isScrollBar = hasScrollbar();
      if (!indicator || !state.isScrollBar) return;
      state.scrollListContent.addEventListener("scroll", handleScroll);
    });
    });

      onBeforeUnmount(() => {
      if (!indicator || !state.isScrollBar) return;
      // 页面卸载,移除监听事件
      state.scrollListContent.removeEventListener("scroll", handleScroll);
    });

      function handleScroll() {
      /**
      * 使用滚动比例来实现同步滚动
      * tips: 这里时一道数学题, 不同的可以把下面的几个参数都打印处理看看
      * 解析一下 这里的实现
      * state.scrollListContent.scrollWidth  内容区域的宽度
      * state.scrollListContent.clientWidth  当前内容所占的可视区宽度
      * state.scrollListIndicatorBar.scrollWidth  指示器的宽度
      * state.scrollListIndicatorBar.clientWidth  当前指示器所占的可视区宽度
      *
      * 内容区域的宽度 - 当前内容所占的可视区宽度 = 内容区域可滚动的最大距离
      * 指示器的宽度 - 当前指示器所占的可视区宽度 = 指示器可滚动的最大距离
      *
      * 内容区域可滚动的最大距离 / 指示器可滚动的最大距离 = 滚动比例 (scale)
      *
      * 最后通过滚动比例 来算出 指示器滚动的 距离 (state.scrollListIndicatorBar.scrollLeft)
      *
      * 指示器滚动的距离 = 容器滚动的距离 / 滚动比例 (对应下面的公式)
      *
      * state.scrollListIndicatorBar.scrollLeft = state.scrollListContent.scrollLeft / scale
      */

      const scale =
      (state.scrollListContent.scrollWidth -
      state.scrollListContent.clientWidth) /
      (state.scrollListIndicatorBar.scrollWidth -
      state.scrollListIndicatorBar.clientWidth);

      state.scrollListIndicatorBar.scrollLeft =
      state.scrollListContent.scrollLeft / scale;
    }

      // 导航到目标路由的方法
      function navigateToRoute(menuItem) {
      // 在这里根据 menuItem 或其他条件构建目标路由的路径
      //const targetRoute = `/your-target-route/${menuItem.id}`; // 示例:根据 menuItem 的 id 构建目标路由
      //console.log(menuItem)
      //JSON.parse(menuItem)
      // 使用 Vue Router 导航到目标路由
      //跳转页面
      router.push(JSON.parse(menuItem));
    }

      function hasScrollbar() {
      return (
      state.scrollListContent.scrollWidth >
      state.scrollListContent.clientWidth ||
      state.scrollListContent.offsetWidth >
      state.scrollListContent.clientWidth
      );
    }

      return {...toRefs(state), width, height, handleScroll, hasScrollbar, navigateToRoute};
    },
    });
      </script>

      <style lang="less" scoped>
      .scroll-list {
      &-content {
      width: 100%;
      overflow-y: hidden;
      overflow-x: scroll;
      // white-space: nowrap;
      display: flex;
      flex-wrap: nowrap;
      /*滚动条整体样式*/

      &::-webkit-scrollbar {
      width: 0;
      height: 0;

    }
    }

      &-group {
      display: flex;
      flex-wrap: wrap;
      // margin-bottom: 16px;
      min-width: 100%;


      &:nth-child(2n) {
      margin-bottom: 0;
    }
    }

      &-item {
      // display: inline-block;
      margin-bottom: 16px;
      text-align: center;
      width: calc(100% / 5);


      &-img {

      width: 44px;
      height: 44px;
      object-fit: cover;
    }

      &-text {
      display: block;
      //font-size: 12px;
      //color: #6B5B50;
      font-family: "Source Han Serif CN", "思源宋体 CN", serif;
      font-weight: normal;
    }

      &:nth-child(n + 5) {
      margin-bottom: 0;
    }
    }

      &-indicator {
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      pointer-events: none; // 禁用滑动指示灯时 滑块滚动
      &-bar {
      width: 40px; // 指示器的默认宽度
      overflow-y: hidden;
      overflow-x: auto;
      white-space: nowrap;
      /*滚动条整体样式*/

      &::-webkit-scrollbar {
      width: 0;
      height: 4px;
    }

      /* 滚动条里面小滑块 样式设置 */

      &::-webkit-scrollbar-thumb {
      border-radius: 50px; /* 滚动条里面小滑块 - radius */
      background: #9b6a56; /* 滚动条里面小滑块 - 背景色 */
    }

      /* 滚动条里面轨道 样式设置 */

      &::-webkit-scrollbar-track {
      border-radius: 50px; /* 滚动条里面轨道 - radius */
      background: #C29E94FF; /* 滚动条里面轨道 - 背景色 */
    }
    }

      &-slider {
      height: 10px;
      min-width: 120px;
      display: block;
    }
    }
    }
      </style>

组件还没完善,但是可以使用,需要增加属性增加的可以自己添加。

2.引入

vue 复制代码
import ScrollList from "@/components/ScrollList/index.vue";

3.注册

vue 复制代码
	components: {
		ScrollList
	},

4.使用

vue 复制代码
	<div class="scrollList-1">
				<ScrollList :list="data" :indicator="true" :indicatorWidth="scrollListWidth" :indicatorBottom="scrollListBottom"
				            iconNum="5"
				            :iconSize="iconSizeKnowledge"/>
			</div>

我是vue3:

vue 复制代码
  	const data = [
			[

				{
					icon: require('../assets/pic/mtzx@2x.png'),
					name: "关注",
					path: JSON.stringify({name: "test", params: {type: 1}})
				},
				{
					icon: require('../assets/pic/mtzx@2x.png'),
					name: "媒体资讯",
					path: JSON.stringify({name: "test", params: {type: 1}})
				},
				{
					icon: require('../assets/pic/mzjs@2x.png'),
					name: "名作鉴赏",
					path: JSON.stringify({name: "test", params: {type: "famous"}})
				},
				{
					icon: require('../assets/pic/jxbd@2x.png'),
					name: "鉴赏宝典",
					path: JSON.stringify({name: "test", params: {type: 5}})
				},
				{
					icon: require('../assets/pic/gyjx@2x.png'),
					name: "工艺赏析",
					path: JSON.stringify({name: "test", params: {type: 3}})
				},

				// 更多项...
			],
			[
				{
					icon: require('../assets/pic/whls@2x.png'),
					name: "文化历史",
					path: JSON.stringify({name: "test", params: {type: 7}})
				},
				{
					icon: require('../assets/pic/rmzs@2x.png'),
					name: "入门知识",
					path: JSON.stringify({name: "test", params: {type: 7}})
				},
				{
					icon: require('../assets/pic/activity.png'),
					name: "活动资讯",
					path: JSON.stringify({name: "test", params: {type: 7}})
				},
				{
					icon: require('../assets/pic/government_information.png'),
					name: "官方公告",
					path: JSON.stringify({name: "test", params: {type: 8}})
				},
				{
					icon: require('../assets/pic/other@2x.png'),
					name: "产业信息",
					path: JSON.stringify({name: "test", params: {type: test}})
				},
				// 更多项...
			],
			// 更多分组...
		];
  
  const scrollListWidth = "60px";// 传递给 ScrollList 的宽度
		const scrollListBottom = "20px"; // 传递给 ScrollList 的指示器底部距离

		const iconSizeKnowledge = ref({
			width: "60px", height: "60px"
		})


return {
			data,
			scrollListWidth,
			scrollListBottom,
			keyword,
			isSearchBoxFixed, famousLampStyle, masterStyle, iconSize, iconSizeJz, iconSizeKnowledge

		};
相关推荐
ZJ_.1 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营5 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood31 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端33 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8537 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
m0_748236111 小时前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235612 小时前
从零开始学前端之HTML(三)
前端·html