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

2023-09-16 23:18:07

效果:

image.png

1.封装组件:

<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.引入


import ScrollList from "@/components/ScrollList/index.vue";

3.注册

	components: {
		ScrollList
	},

4.使用

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

我是vue3:


  	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

		};
更多推荐

Vue2023 面试归纳及复习(2)

1vue3中的动态组件和KeepAlive组件动态组件component<component>动态组件是一种可以根据数据变化而动态加载不同组件的方式。使用动态组件可以有效地减少代码复杂度,提高组件的复用性和灵活性。动态组件通过一个特殊的属性is来实现动态加载,is的值可以是组件的名称或组件对象。KeepAliveKee

设计模式-责任链模式

“单一职责原则”要求一个类仅负责的一个不可分业务逻辑,但这并不意味着能够实现这部分业务逻辑的只能有一个类,业务逻辑可能是会因运行时数据而选择不同类。比如在日常工作中,请假审批可能受请假天数、请假类型等因素影响,而须由不同领导来负责审批。再比如在银行取钱时,取钱业务审批申请可能会受到你所取钱总数、存储类型等因素影响,而须

node 之 express 框架(初级)

一、express热更新1、安装扩展npminstallnode-dev-D2、在根目录下的package.json文件中进行配置3、之后的启动执行下面的命令即可npmrundev二、mvc中的模板引擎1、ejs模板引擎的安装npminstallejs-s2、在根目录下的app.js文件中配置app.set('view

【计算机毕业设计】基于SpringBoot+Vue网络云端日记本系统的设计与实现

博主主页:一季春秋博主简介:专注Java技术领域和毕业设计项目实战、Java、微信小程序、安卓等技术开发,远程调试部署、代码讲解、文档指导、ppt制作等技术指导。主要内容:毕业设计(Java项目、小程序等)、简历模板、学习资料、面试题库、技术咨询。🍅文末获取联系🍅精彩专栏推荐订阅👇🏻👇🏻不然下次找不到哟Sp

C#回调函数学习1

回调函数(CallbackFunction)是一种函数指针,它指向的是由用户自己定义的回调函数。我们将这个回调函数的指针作为参数传递给另外一个函数,在这个函数工作完成后,它将通过这个回调函数的指针来回调通知调用者处理结果。此定义来自网上;看一个例子;这是用委托实现的回调函数;usingSystem;usingSyste

懒人福音!数据显示:剧烈运动5分钟即可预防癌症,风险降低30%

剧烈的间歇性生活方式体育活动,简称VILPA,是由悉尼大学查尔斯·珀金斯中心的研究人员创造的,用来描述我们每天津津有味地进行的非常短暂的活动爆发,每次大约一分钟。这包括剧烈的家务、在杂货店周围大量购物、爆发力量行走或与孩子们玩高能量游戏等活动。早在,2022年12月8日,澳大利亚悉尼大学、英国伦敦大学的研究人员在Nat

ipv6笔记及总结

1、路由器请求消息RouterSolicitation和路由器通告RouterAdvertisement消息主要用于无状态地址的情况下,有状态的情况使用的是dhcpv6server分配(例如:IPv6地址以及其他信息(DNS、域名等))。2、关于IPv6自动配置及M、O比特的小结那么对于自动配置来说,主机在什么情况下,

25 WEB漏洞-XSS跨站之原理分类及攻击手法

目录XSS跨站漏洞产生原理,危害,特点?本质产生层面函数类漏洞操作对应层,危害影响浏览器内核版本等XSS跨站漏洞分类:反射,存储,DOMXSS常规攻击手法:平台,工具,结合其他等演示案例:XSS平台及工具简要演示测试XSS跨站漏洞产生原理,危害,特点?本质跨站跟我们语言上面是没有什么关系,大部分都是前端的一个漏洞在变量

ChatGPT企业版来了,速度翻倍,无使用限制

美国时间8月28日,OpenAI宣布了自ChatGPT推出以来最重大的新闻:将推出ChatGPT企业版,企业版ChatGPT将直接对接GPT-4,提供无限制访问、高级数据分析功能、定制服务等服务,并支持处理更长文本输入的长上下文窗口。OpenAI首席运营官BradLightcap告诉媒体,这个工具已经在“不到一年”的时

logback日志是怎么保证多线程输出日志线程安全的

logback中的单例模式logback日志框架使用了单例设计模式来进行日志输出。在logback中,Logger类是一个关键的组件,它负责记录和输出日志消息。Logger类使用了单例设计模式,确保在一个应用程序中只存在一个Logger实例。这样做的好处是可以确保所有的日志消息都被集中到同一个日志输出器中,避免了多个日

无人机“长坡”上,谁是滚出“厚雪球”的长期主义者?

“股神”巴菲特,曾提出过“长坡厚雪”的理论:人生就像滚雪球,重要的是发现很湿的雪和很长的坡。运用到企业经营上,“长坡”指的是企业所布局的领域发展潜力足、空间大;而“湿雪”,指的是企业竞争力强、有长期主义精神。将湿雪沿着长坡不断滚成厚雪球,就能收获长期主义的复利。就当下来看,在众多领域当中,无人机属于典型的“长坡”。全球

热文推荐