移动端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

		};
相关推荐
罗_三金7 分钟前
前端框架对比和选择?
javascript·前端框架·vue·react·angular
Redstone Monstrosity14 分钟前
字节二面
前端·面试
东方翱翔21 分钟前
CSS的三种基本选择器
前端·css
Fan_web43 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby