商品分类左右联动

news2024/9/18 16:21:59

1、先看效果

2、以hooks方法处理,方便复制使用,见代码

Good.vue文件

<script setup lang="ts" name="goods">
  import {onMounted, ref, nextTick} from "vue";
  import useProductScroll from "@/utils/hooks/useProductScroll.ts";
  import loading from '@/assets/images/loading.gif'
  import categoryHolder from './images/category-holder.png'
  import productHolder from './images/product-holder.png'

  const productList = ref<any[]>([])
  const refSide = ref()
  const refMain = ref()
  const {mark, setMark, setRefSide, setRefMain} = useProductScroll()

  onMounted(() => {
    // 模拟productList的数据
    const tmpList = Array(15).fill(1).map((v, i) => {
      return {
        name: `人气热卖${i}`,
        pList: Array(5).fill(1).map((v2, i2) => {
          return {
            name: `名字${i}-${i2}`
          }
        })
      }
    })
    setTimeout(async () => {
      productList.value = tmpList
      await nextTick()
      setRefSide(refSide.value)
      setRefMain(refMain.value)
      setMark(tmpList[0].name)
    }, 200)
  })

</script>

<template>
  <div class="product-limit">
    <ul class="category-box" ref="refSide">
      <li v-for="v in productList" :key="v" :class="{'active': v.name===mark}" @click="() => setMark(v.name)" :mark="v.name">
        <div>
          <van-image :loading-icon="loading" :src="categoryHolder" lazy-load />
        </div>
        <span>{{v.name}}</span>
      </li>
      <li></li>
    </ul>
    <div class='right-content'>
      <div class="search-box">
        <van-search shape="round" placeholder="请输入商品关键词" />
      </div>
      <ul class="com-product-list" ref="refMain">
        <li v-for="v in productList" :key="v" :mark="v.name">
          <div class="c-category-name">{{v.name}}</div>
          <div class="c-detail-box" v-for="v2 in v.pList" :key="v2">
            <div class="img-show">
              <van-image :loading-icon="loading" :src="productHolder" lazy-load />
            </div>
            <div class="product-tro">
              <p class="c-name">{{v2.name}}</p>
              <p class="c-cut-money">
                <span>立省11.00元起</span>
              </p>
              <div class="c-money">
                <div class="c-money-detail">
                  ¥&nbsp;<i>5.60</i>起&nbsp;
                  <span>¥19.62</span>
                </div>
                <span class="c-btn-size">选规格</span>
              </div>
            </div>
          </div>
        </li>
        <li class="product-holder"></li>
      </ul>
    </div>
  </div>
</template>

useProductScroll.ts文件

import {ref} from "vue";

function getDomStyle(dom: HTMLElement, style: any) {
  return window.getComputedStyle(dom, null)[style];
}

const catchMainMarkDom: {
  [mark: string]: {
    dom: HTMLElement,
    top: number
  }
} = {}
const catchSideMarkDom: {
  [mark: string]: {
    dom: HTMLElement,
  }
} = {}
const catchMainMarkTop: number[] = []
const sideDomView: {
  [key: string]: number
} = {}
let timer:any = null
let io:any = null

export default function useProductScroll() {
  const mark = ref('')
  const refSide = ref()
  const refMain = ref()

  const setRefSide = (d: HTMLElement) => {
    if (getDomStyle(d, 'position') === 'static') {
      d.style.position = 'relative'
    }
    refSide.value = d
    // 判断是否在可视区域
    io = new IntersectionObserver(entries => {
      for (let i=0; i<entries.length; i++) {
        const dom = entries[i].target
        const m = dom.getAttribute('mark')
        sideDomView[m as string] = entries[i].intersectionRatio
      }
    });
    // 缓存
    const children: any = d.children
    for (let i=0; i<children.length; i++) {
      const dom = children[i]
      const mName = dom.getAttribute('mark')
      if (mName) {
        io.observe(dom);
        catchSideMarkDom[mName] = {
          dom: dom,
        }
      }
    }
  }

  const setRefMain = (d: HTMLElement) => {
    if (getDomStyle(d, 'position') === 'static') {
      d.style.position = 'relative'
    }
    refMain.value = d
    // 缓存
    const children: any = d.children
    for (let i=0; i<children.length; i++) {
      const dom = children[i]
      const mName = dom.getAttribute('mark')
      if (mName) {
        catchMainMarkDom[mName] = {
          dom: dom,
          top: dom.offsetTop
        }
        catchMainMarkTop.unshift(dom.offsetTop)
      }
    }
    // 绑定
    refMain.value.addEventListener('scroll', bindMainScroll)
  }

  const setMark = (str: string) => {
    mark.value = str
    const dom = catchMainMarkDom[str]['dom']
    dom.scrollIntoView({
      behavior: "smooth"
    });
  }

  const bindMainScroll = (e: any) => {
    clearTimeout(timer)
    const scrollTop = e.target.scrollTop
    timer = setTimeout(() => {
      // 判断top值和scroll比较,获取最近的top值,获取新的mark值
      let newTop = 0
      for (let i=0; i<catchMainMarkTop.length; i++) {
        if (scrollTop >= catchMainMarkTop[i]) {
          newTop = catchMainMarkTop[i]
          break;
        }
      }
      let markName = ''
      // 通过newTop值,获取新的mark名称
      for (let mName in catchMainMarkDom) {
        if (catchMainMarkDom[mName]['top'] === newTop) {
          markName = mName
          break
        }
      }
      if (mark.value === markName) return;
      mark.value = markName
      const isView = sideDomView[markName] > 0
      if (!isView) {
        catchSideMarkDom[markName]['dom'].scrollIntoView({
          behavior: "smooth"
        });
      }
    }, 200)
  }

  const unbind = () => {
    refMain.value.removeEventListener('scroll', bindMainScroll)
    io?.disconnect()
    io = null
  }

  return {
    mark,
    setMark,
    setRefSide,
    setRefMain,
    unbind
  }
}

3、使用,hooks抛出了5个方法,作用分别是:
mark:标示字符,用于判断分类

setMark:当分类点击时,传入mark值

setRefSide:传入dom元素,分类的scroll元素

setRefMain:传入dom元素,商品的scroll元素

unbind:组件卸载时调用

4、使用规则

a、用原生滚动

b、需要在页面渲染后使用

c、依次调用setRefSide,setRefMain,setMark

d、在分类列表和产品列表,scroll元素的子元素,需要绑定mark标示,用于匹配

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

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

相关文章

halcon序列化机制

可以结合halcon算子的.net程序进行面向对象的编程&#xff1a; 源码如下&#xff1a; 打开算子的.net程序&#xff1a; 将程序运用到C#中&#xff1a; halcondonet.dll源码解读 halcon与C#联合编程的demo halcon的序列化机制 采用二进制进行序列化保存和反序列化 步骤&#…

GigE Vision GVCP/GVSP

GIGE协议&#xff0c;全称Gigabit Ethernet Vision协议&#xff0c;是一种基于千兆以太网&#xff08;Gigabit Ethernet&#xff09;技术开发的相机接口标准&#xff0c;主要用于高速图像采集和处理。该协议通过以太网技术实现图像数据和控制信号的传输&#xff0c;具有低成本、…

【Conda】命令大全 + 包安装报错一招解决

conda常用命令总结 一、conda常用命令大全 命令用法命令获取版本号conda -V conda --version获取帮助conda -h conda --help获取环境相关命令的帮助conda env -h所有 --单词 都可以用 -单词首字母来代替比如 -version 可以用 -V来代替&#xff0c;只不过有的是大写…

float、double

按照这个规定&#xff0c;单精度浮点数&#xff08;float&#xff09;这个数据类型所占内存大小为4个字节&#xff0c;也就是32位&#xff0c;所以单精度浮点数也叫32位浮点数&#xff0c;它在内存或硬盘中要占用32个比特。 单精度浮点数的尾数部分用23位存储&#xff0c;加上默…

贝叶斯估计模型及 Stata 具体操作步骤

目录 一、引言 二、贝叶斯估计的理论原理 三、Stata 代码示例 四、结果解读与分析 一、引言 贝叶斯估计作为一种强大的统计推断方法&#xff0c;在结合先验信息和样本数据以获得更准确的参数估计方面具有显著优势。本文将深入探讨贝叶斯估计的理论原理&#xff0c;并通过 St…

汇聚荣做拼多多电商怎么样?

汇聚荣做拼多多电商怎么样?在当前电商平台竞争激烈的背景下&#xff0c;拼多多凭借其独特的商业模式和市场定位迅速崛起。对于想要加入拼多多的商家而言&#xff0c;了解平台的特点、优势及挑战是至关重要的。本文将深入分析加入拼多多电商的多个方面&#xff0c;帮助读者全面…

网站外链还有没有作用

前言 还记得“内容为王&#xff0c;外链为皇”这句话吗&#xff1f;在以前网站外链是网站优化中非常主要的环节。那时候做一个网站&#xff0c;只要不停的发外链&#xff0c;收录就不会差&#xff0c;于是大部分站长都使劲发外链。 有市场就有商场&#xff0c;大家都看到外链…

昇思25天学习打卡营第18天|MindNLP ChatGLM-6B StreamChat

MindNLP ChatGLM-6B StreamChat MindNLP ChatGLM-6B StreamChat是基于MindNLP框架和ChatGLM-6B模型实现的聊天应用&#xff0c;利用自然语言处理技术&#xff0c;实现与用户的自然语言交流。这样的应用可以广泛应用于智能客服、在线助理和社交聊天等场景。 在当前技术环境下&a…

大数据------JavaWeb------VueElement(完整知识点汇总)

Vue 定义 Vue是一套前端框架&#xff0c;可以免除原生JavaScript中的DOM操作&#xff0c;简化书写 之前所学的MyBatis框架是用来简化JDBC代码编写的&#xff1b;而Vue是前端框架&#xff0c;用来简化JavaScript代码编写的 在Axios与JSON综合案例的添加中有大量的DOM操作&#…

设备运维、教学直播...浅析远程控制在医疗专网环境下的应用

在医疗行业内&#xff0c;无论是高端的医疗设备&#xff0c;还是医疗机构使用的各种数字化系统&#xff0c;出于安全考虑往往都搭建在专网内网之中&#xff0c;无法直接与外网连接。在这种情况下&#xff0c;常规的远程控制变得很难接入到医疗业务中。 但另一方面&#xff0c;…

TP5 封装通用的微信服务类

1、安装依赖包 我们这里用的是 EasyWeChat EasyWeCha官网 https://www.easywechat.com/ 安装地址 https://github.com/easywechat/docs 相关文档 https://www.easywechat.com/docs/4.1/payment/index composer安装 $ composer require overtrue/wechat:~4.0 -vvv1、封装服务类 …

matlab R2016b安装cplex12.6,测试时cplex出现出现内部错误的解决方法

问题场景 网上搜索matlabyalmipcplex的安装教程&#xff0c;跟着步骤操作即可&#xff0c;假如都安装好了&#xff0c;在matlab中测试安装是否成功&#xff0c;出现以下问题&#xff1a; 1、matlab中设置路径中添加了yalmip和cplex路径&#xff0c;在命令窗口中输入yalmiptest…

[Flask笔记]一个完整的Flask程序

前面讲过Flask是一个轻量级Web开发框架&#xff0c;为什么说是轻量级的呢&#xff0c;因为它用短短几行代码就能运行起来&#xff0c;我们一起来看看最简单的flask框架。 安装Flask 在看Flask框架之前我们需要先安装flask模块&#xff0c;学过python的肯定都知道&#xff0c;…

python3读取shp数据

目录 1 介绍 1 介绍 需要tmp.shp文件和tmp.dbf文件&#xff0c;需要安装geopandas第三方库&#xff0c;python3代码如下&#xff0c; import geopandas as gpdshp_file_path "tmp.shp" shp_data gpd.read_file(shp_file_path) for index, row in shp_data.iterro…

久期分析与久期模型

目录 一、久期分析的理论原理 二、数据准备 三、Stata 程序代码及解释 四、代码运行结果 一、久期分析的理论原理 久期&#xff08;Duration&#xff09;是衡量债券价格对利率变动敏感性的重要指标。它不仅仅是一个简单的时间概念&#xff0c;更是反映了债券现金流回收的平均…

最新 Kubernetes 集群部署 + Contranerd容器运行时 + flannel 网络插件(保姆级教程,最新 K8S 1.28.2 版本)

资源列表 操作系统配置主机名IP所需插件CentOS 7.92C4Gk8s-master192.168.60.143flannel-cni-plugin、flannel、coredns、etcd、kube-apiserver、kube-controller-manager、kube-proxy、 kube-scheduler 、containerd、pause 、crictlCentOS 7.92C4Gk8s-node01192.168.60.144f…

BGP第三日谈

今日所用拓扑 先补充昨日没有讲到的知识点&#xff1a; 1.IBGP有更新源检测机制 这种机制使得BGP路由在IBGP邻居间传递时下一跳地址仍然保持不变&#xff0c;但是IBGP却没有去往下一跳地址的路由&#xff0c;所以我们需要手动将IBGP邻居间传递的BGP路由下一跳地址转成与IBGP…

星辰考古:TiDB v4.0 进化前夜

前情回顾TiDB v4 时间线TiDB v4 新特性 TiDBTiKVPDTiFlashTiCDCTiDB v4 兼容性变化 TiDBTiKVPD其他TiDB 社区互助升级活动TiDB 3.0.20 升级到 4.0.16 注意事项升级速览直观变化总结素材来源&#x1f33b; 往期精彩 ▼ 前情回顾 在前面的章节中&#xff0c;我们共同梳理了 TiDB …

【ARM】CCI缓存一致性整理

目录 1.CCI500提供的功能 2.CCI500在SOC系统中所处的位置​编辑 3.CCI500内部结构​编辑 4.功能描述 1.CCI500提供的功能 2.CCI500在SOC系统中所处的位置 3.CCI500内部结构 Transaction Tracker&#xff08;TT&#xff09;是用来解决一致性和ordering问题的&#xff0c;它…

光伏仿真系统推荐

在全球能源转型和绿色能源发展的背景下&#xff0c;光伏行业作为重要的绿色能源组成部分&#xff0c;其智能化、数字化的发展显得尤为关键。光伏仿真系统作为提升光伏项目设计、运维效率的重要工具&#xff0c;在行业中扮演着不可或缺的角色。在众多光伏仿真系统中&#xff0c;…