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

news2024/10/7 4:36:41

效果:

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

		};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1033839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

子序列问题集合

子序列问题 最长公共子序列&#xff1a;最长上升子序列&#xff08;要输出序列&#xff0c;和最大长度&#xff09;1.dp2.贪心二分 导弹拦截 &#xff08;最长上升/下降子序列长度&#xff09; 最长公共子序列&#xff1a; class Solution { public://dfs(i,j)代表s串i前字符和…

C语言自定义类型详解(1)结构体知识汇总

本篇概要 本篇主要讲述C语言结构体的相关知识&#xff0c;包括结构体的基本声明&#xff0c;结构体的匿名结构&#xff0c;结构体的自引用&#xff0c;结构体变量的定义和初始化以及结构体的内存对齐等相关知识。 文章目录 本篇概要1.结构体1.1结构体的基本声明1.2结构体的特殊…

应用程序转换工具Unite mac中文版软件特点

Unite mac是一款Mac平台上的应用程序转换工具&#xff0c;它可以将任何网站或Web应用程序转换成本地应用程序&#xff0c;以便更方便地访问和使用。 Unite mac软件特点 网站转应用程序&#xff1a;该软件可以将任何网站或Web应用程序转换成本地应用程序&#xff0c;方便用户更…

AI创作工具-AI创作工具技术解读

创作是广告、文章、小说、社交媒体内容等各个领域的关键&#xff0c;但它通常需要创作者花费大量时间和精力&#xff0c;思考、编写和编辑内容。有时候&#xff0c;创作者可能面临写作灵感枯竭、时间紧迫或需要大量内容的情况。 添加图片注释&#xff0c;不超过 140 字&#xf…

Java集合--Collection、Map、List、Set、Iterator、Collections工具类

文章目录 一、Java集合框架概述二、Collection接口方法2.1、Collection子接口一&#xff1a;List2.1.1、ArrayLIst的源码分析2.1.2、LinkedList的源码分析2.1.3、List接口中的常用方法 2.2、Collection子接口二&#xff1a;Set2.2.1、Set接口的无序性2.2.2、添加元素的过程&…

Leetcode---363周赛

题目列表 2859. 计算 K 置位下标对应元素的和 2860. 让所有学生保持开心的分组方法数 2861. 最大合金数 2862. 完全子集的最大元素和 一、计算k置为下标对应元素的和 简单题&#xff0c;直接暴力模拟&#xff0c;代码如下 class Solution { public:int sumIndicesWithKS…

从零开始学习CTF,看完不信你学不会!

一、CTF简介 简介 中文一般译作夺旗赛&#xff08;对大部分新手也可以叫签到赛&#xff09;&#xff0c;在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式 CTF起源于1996年DEFCON全球黑客大会&#xff0c;以代替之前黑客们通过互相发起真实攻击进行技术…

Linux——文件系统

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;Linux——文件系统 ☂️<3>开发环境&#xff1a;Centos7 &#x1f4ac;<4>前言&#xff1a;上期我们了解了文件在内存中得组织方式&#xff0c;那么文件在磁盘中…

ElasticSerach+MongoDB:实现文章检索历史功能

实现目标&#xff1a; 展示用户的搜索记录10条&#xff0c;按照搜索关键词的时间倒序可以删除搜索记录保存历史记录&#xff0c;保存10条&#xff0c;多余的则删除最久的历史记录 数据库的选择&#xff1a; 用户的搜索记录&#xff0c;需要给每一个用户都保存一份&#xff0c;数…

第3讲:vue路由安装配置,带参路由,子路由配置及应用

路由的安装与基本使用 vue-router是Vue官方的路由插件,它和Vue是深度集成,适用于构建单页面应用 vue-router的安装 //vue2路由配置npm install vue-router@3.0.2 或 cnpm install vue-router@3.0.2在具体应用的开发中我们一般会在src目录下新建一个名为router的目录,并在r…

加速乐源码(golang版本)

一、分析 分析过程网上有很多,这里只说个大概,主要是提供golang源码 请求网站,发现前两次请求都会返回521,第三次请求成功,说明前两次请求肯定是干了什么事情;使用接口请求工具模拟请求分析该过程 使用postman工具请求 a. 第一次请求会在响应头返回jsluid,返回内容中拼接…

滚动轴承 调心球轴承 外形尺寸

声明 本文是学习GB-T 281-2013 滚动轴承 调心球轴承 外形尺寸. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了符合 GB/T 273.3—1999 的调心球轴承及带紧定套的调心球轴承(以下简称轴承)的 外形尺寸。 本标准适用于调心球轴承…

【数字IC/FPGA】基于握手的数据广播

简介 本文介绍了一种基于握手协议的数据广播方式。事实上,该场景可以简化为模块的一对多握手,并且下游的各个模块可以独立的完成握手(与之相对的是下游模块一起完成握手,相比之下,下游各个模块可以独立完成握手显然更加普适、灵活)。 下图展示了模块一对多握手的示意图:…

阿里云服务器经济型e实例详细介绍_e系列性能价格表

阿里云服务器ECS推出经济型e系列&#xff0c;经济型e实例是阿里云面向个人开发者、学生、小微企业&#xff0c;在中小型网站建设、开发测试、轻量级应用等场景推出的全新入门级云服务器&#xff0c;CPU采用Intel Xeon Platinum架构处理器&#xff0c;支持1:1、1:2、1:4多种处理…

GLTF编辑器如何合并相同材质的Mesh

1、什么是模型材质合批 模型材质合批是一种技术手段&#xff0c;主要用于优化渲染性能和提高图形应用程序的帧率。它通过将多个模型的材质进行合并&#xff0c;从而减少渲染时的绘制调用次数。 在计算机图形学中&#xff0c;每个模型都有一个或多个材质&#xff0c;这些材质定义…

【教程】视频汇聚/视频监控管理平台EasyCVR录像存储功能如何优化?具体步骤是什么?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。视频监控系统EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、…

使用 PyTorch 的计算机视觉简介 (5/6)

一、说明 本文主要介绍CNN中在pytorch的实现&#xff0c;其中VGG16网络&#xff0c;数据集来源&#xff0c;以及训练过程&#xff0c;模型生成和存储&#xff0c;模型调入等。 二、预训练模型和迁移学习 训练 CNN 可能需要大量时间&#xff0c;并且该任务需要大量数据。但是&am…

【每日一题】441. 排列硬币

441. 排列硬币 - 力扣&#xff08;LeetCode&#xff09; 你总共有 n 枚硬币&#xff0c;并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯&#xff0c;其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。 给你一个数字 n &#xff0c;计算并返回可形成 完整…

位段 联合体 枚举

Hello好久不见&#xff0c;今天分享的是接上次结构体没有分享完的内容&#xff0c;这次我们讲讲位段 枚举和联合体的概念以及他们的用法。 2.1 什么是位段 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int 。 …

Unity下tga和png格式图片打包成AB包大小和加载速度测试

测试素材 测试素材&#xff0c;一张tga格式&#xff0c;一张png格式&#xff0c;他们的图像尺寸一样都是8K图。 两张图在AssetBundles里显示 Tga格式的图明显大很多&#xff0c;我们打包成ab包看看。 在PC 打包后看&#xff0c;明显大小一样&#xff0c;我们进行ab包加载&am…