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

效果:

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

		};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的移动端 H5 列表页的 HTML 和 CSS 代码,其中包含淡灰色背景和蓝色标题: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>列表页</title> <style> body { background-color: #f7f7f7; /* 淡灰色背景 */ margin: 0; padding: 0; font-family: Arial, sans-serif; } .header { background-color: #1e90ff; /* 蓝色标题 */ color: white; padding: 20px; text-align: center; font-size: 24px; font-weight: bold; } .list { margin: 20px; padding: 0; list-style: none; } .list-item { background-color: white; margin-bottom: 10px; padding: 10px; border-radius: 5px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .list-item-title { color: #1e90ff; /* 蓝色标题 */ font-size: 20px; margin: 0; padding: 0; } .list-item-description { color: #666; font-size: 16px; margin: 5px 0 0 0; padding: 0; } </style> </head> <body> <div class="header">列表页</div> <ul class="list"> <li class="list-item"> <h2 class="list-item-title">列表项标题 1</h2> <p class="list-item-description">列表项描述 1</p> </li> <li class="list-item"> <h2 class="list-item-title">列表项标题 2</h2> <p class="list-item-description">列表项描述 2</p> </li> <li class="list-item"> <h2 class="list-item-title">列表项标题 3</h2> <p class="list-item-description">列表项描述 3</p> </li> </ul> </body> </html> ``` 在这个代码中,`.header` 类代表页面顶部的标题,`.list` 类代表列表本身,`.list-item` 类代表每一个列表项,`.list-item-title` 类代表每一个列表项的标题,`.list-item-description` 类代表每一个列表项的描述。你可以根据自己的需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值