广州总部电话:020-85564311
广州总部电话:020-85564311
20年
互联网应用服务商
请输入搜索关键词
知识库 知识库

优网知识库

探索行业前沿,共享知识宝库

淘宝购物车拖拽功能的思考与实践

发布日期:2025-08-18 17:27:54 浏览次数: 810 来源:大淘宝技术
推荐语
淘宝购物车拖拽功能如何提升购物效率?技术实现与用户体验的完美结合。

核心内容:
1. 购物车拖拽功能的设计背景与用户需求分析
2. 拖拽规则的技术实现与数据结构设计
3. 复杂业务场景下的交互优化与性能保障
小优 网站建设顾问
专业来源于二十年的积累,用心让我们做到更好!
图片



本文深入剖析了购物车拖拽功能的技术实现路径,在复杂业务场景下通过精细化控制、合理架构分工与细节体验打磨,实现了高效、稳定、流畅的用户交互体验



图片
功能演示

拖拽演示
   Weex demo   
购物车场景

为什么需要拖拽?

  2.1 购物车功能 & 分组与拖拽的关系

购物车作为用户准备购买的商品集合,承担着展示+管理的功能。

上述管理操作都是针对单一商品,为了丰富对于所有购物车商品的管理形式,在 iCart 版本新增了和店铺并列的分组概念。允许用户把不同店铺内的不同商品放置在同一个区块内,提升勾选、凑单、结算的效率。

视图

  2.2 拖拽的用途 & 拖拽规则

拖拽是针对用户购物车默认结构的一种调整,支持用户自动商品和区块组合起来。当然这种组合也不是全的任意组合,也是有一些规则限制的。

简单罗列一下大致有这些规则:商品支持移入、移出自身店铺及任意分组;分组、店铺仅支持前后排序。一个商品特例是搭配购,搭配品必须一起移动,中间不能有任何品。
  • 同一店铺内商品间可以随意排序,
  • 同一分组内的商品可以随意排序,
  • 店铺里的商品可以放进任何一个分组,
  • 凑单条必须紧跟着店铺头
  • ······

iCart 通过定义这样数据类型,用来表述用户的一次拖拽行为
 {  type: reorderGroup|reorderInGroup|joinIntoGroup|removeFromGroup   // 当前操作的类型  fromBundleId:  XXXXXXX                                            // 来自哪个分组/店铺  toBundleId:    XXXXXXX                                            // 去往哪个分组/店铺  from:          XXXXXXX                                            // 操作品的cartId  to:            XXXXXXX                                            // 松手处上方的cartId}

  2.3 拖拽功能的现实映射(为了更好的理解拖拽规则)

规则描述起来较为抽象,为了更好理解店铺、分组、商品之间的关系,理解拖拽中一些限制的原因,可以使用以下场景来映射一下购物车列表。以车厢、乘客为例,定义一些概念。

  • 现实映射

概念
  • 车厢:一种是 VIP 号码车厢<店铺>;一种是普通车厢<分组>
  • 车厢门:每个车厢有两个门,分布在车厢两侧<店铺头>;有铰链连接后一车厢<分隔符>
  • 窗帘:仅 VIP 号码车厢有窗帘<凑单条>,普通车厢没有
  • 乘客:
  • 普通乘客<商品>:每人都有一张 VIP 卡,对应一个 VIP 车厢号
  • 绑定乘客<搭配购商品>:乘客 A1、A2、A3。 他们不能被分开(中间不能被插入任何乘客;始终在同一车厢)

那这种模型在更改顺序时会有什么规律,或限制呢?
  • 仅有一个限制:VIP A 的乘客,无法进入 VIP B 车厢

考验大家两个选择题
  • 若持有 VIP A 卡号的乘客在 VIP C 和 VIP D 间停留,铁路系统会如何分配?
  • 进入 VIP C?
  • 进入 VIP D?
  • 在车厢 C D 之间新建一个普通车厢✅
  • 如果 VIP B 车厢内仅有一个乘客 B,该乘客进入了普通车厢 2,铁路系统如何分配?
  • 保留 VIP B 车厢?
  • 删除 VIP B 车厢、窗帘等附属节点✅
  • 如果 VIP C 车厢里来了一位乘客站在了窗帘和门之间,该如何调节?
  • 就放在那,形成【门、人、窗帘】的排序
  • 帮他挪出窗帘,形成【门、窗帘、人】的排序✅

主体方案拆解

  3.1 操作全流程拆解

拖拽就是指把一个区块提起来,上下移动后放到想要放置的位置。购物车将整个拖拽流程拆分成三个步骤,各自实现对应功能。

  1. 长按触发,长按组件触发拖拽逻辑,生成对应组件的缩略版,同时折叠需要隐藏的组件

  2. 拖拽移动,滑动组件,根据悬停位置和 list 底部的元素进行交换,如果触及屏幕边缘,主动滚动列表

  3. 松手放置,松手或者切换 tab、锁屏,意为退出本地拖拽,根据当前位置放置元素和发送网络请求




  3.2 长按 CreateProxyChild

长按时,我们需要将用户长按的这个元素悬浮起来,给一种被拿起来的感觉。考虑到悬浮起来的卡片与原始态的卡片样式差别较大,我们分两步来实现该效果。首先隐藏长按的元素,其次生成 ProxyChild 替换消失的元素,在视觉上达到悬浮的效果。只要一帧完成,就能给用户提起的感觉。

1. 生成跟着用户手指的悬浮 HTMLElement
  1. 根据用户当前长按的元素,判断该元素是商品、搭配购商品、还是分组、店铺。各自路由到对应的缩略版元素上即可。
2. 微微震动,给用户一些友好的提示


正常态

管理态(简化版)

拖拽状态(缩略版)

正常商品

店铺


  3.3 移动 onHover

用户移动过程中,需要频繁的计算当前位置与 list 中每一个元素的碰撞情况,这个逻辑放在前端会比较重(要计算每个 cell 的高度,计算触摸位置,给每个 cell 设置位移动画,手动设置 list 下滑,触发 loadmore 等等)。因此把这部分的位移动画、碰撞逻辑交给 Weex 实现,前端只需要关心在加载下一页的过程中,设置好正确的可放置属性就好了。

为了在移动过程中,能够显示针对该用户的拖拽提示,在每次与其他 cell 碰撞的时候,都会回调一次前端注册的 onHover 事件,病传递给前端一个碰撞 index,前端基于此可以获取到目前元素的悬浮位置,比对过停留位置与悬浮元素的对应关系后,可以给予用户在当前位置停留时的提示(例如交换位置、创建新组、不支持放在该组等)。

  1. 拖到可以放置位置的时候,Weex 将底层 list 里的元素腾出可以被放置的位置
  2. 在 onHover 事件中判断放置的类型,给用户相应的提示

  3.4 放置 onReorder

和 onHover 事件的设计思想一致,当用户 onHover 状态转换成松手状态时,前端依然根据碰撞 index 来计算对应位置,将拖动的元素放到对应位置上,并发送相应请求来更新整个列表。如果拖动的是整个店铺,还需要额外把店铺内的商品展开。

复杂业务逻辑揭秘

为什么说常常有人说购物车拖拽这块的业务表现复杂? 综合考虑购物车业务形态,我觉得确实有以下几点客观原因:

复杂因素
  • list 中的拍平列表与视觉上的树形结构映射较为复杂(一维列表操作二维树形结构)
  • 分组、搭配购,店铺组件折叠及互斥逻辑
  • 非可拖拽元素的兼容处理(头部组件、信息流、分割线)
  • 底部信息流处理(嵌套列表)
  • 管理态度下 height = 0 的隐藏元素的参与(凑单条、筛选项)

简单列举一下这些复杂度结合起来会发生什么
拖起元素
放置位置
响应事件
单品 / 搭配购
店铺与店铺间的灰色分隔上
removeFromGroup
(店铺 | 分组)里前 N 个位置
joinIntoGroup/reorderInGroup
(店铺 | 分组)最后一个靠外的位置
removeFromGroup
凑单条上
removeFromGroup
筛选项 / 信息流上
removeFromGroup
分组 / 店铺
分组与分组的灰色分隔上
reorderGroup
筛选项 / 信息流上
reorderGroup

这一节 挑选几个比较复杂的逻辑描述一下实现的难点

  4.1 不可放置的逻辑是怎么实现的?

如上文所说,二级结构被 list 打平成了并列结构。 针对 A 店铺的商品不可以被放置在 B 店铺的商品中的情况,该如何实现呢?给每一个 list 的一级子节点新增一个 reorderable 属性,为 true 代表可被放置在下方,为 false 代表不可以被放置。所有的可被放置判断均以是否被挪到下方为准。

  • 例如现有 1、2、3、4、5、6 个 cell, 将 3 设置为 reorderable: false, 当我拖动 5 往上移动时,移动到 3 和 4 之间时,1234-6 均不会移动,移动到 2-3 之间时,列表会变成 12-346。这是因为 3 的可放置属性为 false,因此不会响应用户悬停在 3-4 之间的逻辑。

依靠这一单一属性的设置,可以完美做到针对店铺、品、搭配购等的互斥效果。另外该属性也是可以动态调节的,比如用户在长按拖起的时候,会更新当前所有 list 中元素的 reorderable 属性

  4.2 如何知道用户想要放置的位置(怎么屏蔽凑单条的影响)

3.3 节描述在用户松手时,Weex 会注入一个 index,标志目前被放置的位置。前端根据此 index 会找到对应元素的类型,将 item、shop、group、promotion 类型进行比对后,可以得到当前可放置的最合适区域。

为了与 Weex 提供的 index 对应起来,在用户进入管理态前,购物车会初始化一个从上到下包含 list 全部元素的数组,在维护可视区关系的同时,用来判断放置位置的最近可放置元素。 依靠这个数组与放置 index,可以自动屏蔽不可被放置的位置的影响,防止出现商品被放在 promotion 和 shop 中间、或者 shop 被放在另一个 shop 里面的情况。

  4.3 信息流、筛选项等非可拖动的 cell 如何处理?

沿袭 4.2 的理论,管理态下购物车使用 dom 操作隐藏了凑单栏与筛选项。但他们仍然会被 Weex 用来当做交换位置的一份子,当用户“特意”放置在这些非法区域时,可以凭借专属判断和上述可视区数组将他们屏蔽掉,并计算可视区的列表顺序把他们放在正确的位置。

  • 针对凑单条:

    我们将高度设置为 0px 来隐藏,在用户放置时通过可视区列表来保障放置位置的准确性。

  • 针对顶部筛选项、催领条:

    我们在管理态保留 1px 的高度,既可以响应商品拖至顶部的操作,也能当用户正好放置在此处时,根据是否是 filter 组件来判断是否需要调整放置位置,如果需要调整,将该商品放置在第一个商品的位置。

  • 针对底部的信息流(appear 锚点、错误页、卡片等):

    通过 feeds 判断,将视图放置在信息流处的商品通通放置在最后一个商品后,避免出现商品放在信息流下面的异常情况。

  • 针对信息流嵌套列表:

    嵌套列表可能导致无限滚动,我们设置拖拽时触发的 list 滚动,属于外层容器滚动,而嵌套的信息流为内侧滚动容器,因此就算用户拖起后一直停留在屏幕底部,也不会触发最多一屏的信息流滚动,避免了拖拽时无限滚动的情况发生。


体验细节优化 - 以异常抖动为例

分组拖拽具体逻辑:
  • 长按店铺头时,会主动收起该店铺的所有商品。视觉上的表现就是将组头变成缩略图,下方的商品卡隐藏。
  • 放置整个店铺时,会将组头恢复,并展开所有的店铺商品。视觉上的表现就是,就是将缩略图恢复成组头,同时展示商品卡。

这里的难点在于需要保证四部分逻辑的良好衔接
  • 店铺头和悬浮元素的切换
  • 商品卡的收起
  • Weex 的放置动画
  • 下方元素与拖动区域的间距不突变

  5.1 分组拖起过程

先看一下遇到的问题录屏

    修复前效果    

修复后效果


假设组头高度 100,商品卡高度 200,分割红框高度 100,组头拖起后的悬浮元素高度为 200。视频里的问题可以大致描述为:整个拖起分组的高度会瞬间增高,商品卡高度再逐渐缩小为 0。

逐帧分析这个出问题的录屏
  1. 时刻 1:高度 400。在用户长按前。100 + 200 +100

  2. 时刻 2:高度 500。长按生成悬浮元素的一瞬间。200 + 200 + 100  (组头 100 上方显示了悬浮元素,导致整个列表往下抖动了)

  3. 时刻 3:高度 500。长按生成悬浮元素后,隐藏多余元素。与时刻 2 的高度一致 商品卡+红框被隐藏,但下方的店铺并未及时跟上

  4. 时刻 4:高度 100。长按逻辑结束。 整体变成了 100 的高度(恢复成了组头原本的高度,导致下方列表被盖住了)



预期情况应该是在长按后,原本高度为 500(100+200+100)的整个店铺区域一帧变成高度为 200 的悬浮元素,且下方店铺紧随悬浮元素后。

具体原因:
  1. 在商品卡高度未改变时,列表中的拖起元素的高度会被设置为 proxyChild 的

    高度

  2. 因为商卡高度未变、拖起元素占位高度变高,因此会导致 list 的下方元素先闪到下面,再因为商卡高度变小逐渐移动到上方。

解法:

需要后置拖起元素在 list 的占位高度变为 proxyChild 高度的时机,在

商卡高度减小的同时执行。

  5.2 分组放置过程

修复完拖起的高度问题后,再看一下放置时的抖动录屏

    修复前录屏    

修复后录屏


视频里的问题可以大致描述为:松手时整个拖起组的高度增高,且悬浮元素消失时分组头的高度才会恢复。
  1. 时刻 1:高度 200 在用户松手前。悬浮元素高度 200, 列表占位 200。

  2. 时刻 2:高度 600 松手的一瞬间,悬浮元素消失,商品卡展示。展示占位 200 的元素+两个商品卡。(200+200*2)

  3. 时刻 3:高度 500恢复商品卡的展示。高度恢复为拖动前的高度。(100+200*2)

预期情况应该是在松手后,原本高度为 200 的整个悬浮区域一帧变成高度为 500 的整个店铺,且下方店铺紧随悬浮元素后。


具体原因:

  1. 在松手、且商品卡高度未改变时,列表中的占位的高度会被设置为 proxyChild 的高度

  2. 因为商卡高度未变、拖起元素占位高度变高,因此会导致 list 的下方元素先闪到下面,再因为占位恢复时回归本身的位置。

解法:

需要前置拖起元素在 list 的占位高度恢复为原生高度的时机,在商卡高度变高的同时执行。


  5.3 最终的统一解决方案:优化回调时机

长按时:规定长按时和生成悬浮元素的 API 。Weex 在 CreateProxyChild 中仅处理生成悬浮元素的逻辑,前端在 onReorderStart 时操作店铺内的商品隐藏。在 Weex 侧将这两个时机统一起来,在 proxyChild 生成的同时切换 onReorderStart 的逻辑,达到一帧切换、高度不闪动的状态。

松手时统一松手后的 API 设计。之前存在 onReorderEnd 与 onReorder 两个时机。 前端会在 onReorderEnd 中做前端的恢复展示逻辑,在 onReorder 处理换位置后的插入逻辑。 这两个时机不同导致渲染时存在一个先后顺序,时机上无法统一。 将这两个时机统一收敛到 onReorder 中,前端把所有的逻辑放在 onReorder 中,并在 Insertbefore 后调用 endReorder 通知 Weex 调顺序完成,保证 Weex 复位动画和商品展示在同一时机执行,视觉效果不抖。


  5.4 卡顿优化


在开发初期,Weex 交付的版本在拖拽触发列表滑动的过程中总一抖一抖的,显得十分不流畅。 看似是帧率问题,其实本质原因是自动滚动的时候每次的位移不一样导致的。在本身帧率并不低的情况下,每次滑动 Weex 给定的位移距离一会大一会小,看上去就会显得十分卡顿了。后续增加每次位移距离一致,且随着离屏距离渐变的逻辑后,就达到了右侧流畅拖动的效果。

(其实这个问题解决起来远没有其他问题复杂,但它却十分影响体验。所以做体验的老师们要谨记,一个小小的逻辑漏洞就会导致用户体验的崩塌...)

顾与展望

  6.1 回顾

复盘整个拖拽开发流程,我们总是在一级列表上寻找方法来支持二级列表的调结构功能,虽然设计的 API 是简化了,但是徒增了业务代码的理解难度。 如果在 list 中直接嵌入组的概念,是不是会更加简单?

相比与列表的横滑,拖拽对于整体列表的影响会更大,长距离的 diff,组件交互等极限场景会放大 DOM diff 的某些缺陷,这种大型改造的 crash 风险还是有的,需要提前做好预判。

  6.2 展望

在拖拽这种重交互的场景下,Weex 与前端两者协作的最佳分工应该是 Weex 提供频繁计算与复杂布局的底层能力,前端依赖交互接口实现定制的业务逻辑。目前的实现方案正好,在合理划分了二者职责边界的情况下,也最大程度上发挥了二者的能力。

拖拽全流程概览


团队介绍


本文作者迭活,来自淘天集团-交易终端技术团队。本团队负责手淘 App 交易链路业务研发与基础能力建设。面向亿级 DAU 核心业务,我们以稳定、性能与体验为第一原则,深耕原生 Native、跨端渲染引擎与前端 Web 技术,结合「新奥创」端到端方案,驱动业务高效迭代与快速交付。团队自研 JSTracker 数据监控平台,打造可观测、可优化的交易基础设施;同时前瞻布局 AI,探索 AI Native 应用与 AI 编码实践,将智能融入研发全流程,为用户提供极致、可靠的购物体验。

2026 秋季校园招聘火热进行中,前端&客户端简历投递:LF101548@alibaba-inc.com。交易服务端、质量与数据科学岗位也在同步招聘,有兴趣参与世界级分布式交易系统、实时计算引擎、高并发架构设计与优化;深入交易支付核心电商业务场景,用代码直接撬动万亿GMV增量的同学速来。



¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术
服务端技术 | 技术质量 | 数据算法



优网科技,优秀企业首选的互联网供应服务商

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!

优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、微信小程序定制开发、移动端应用(手机站APP开发)、微信定制开发(微信官网、微信商城、企业微信)等一系列互联网应用服务。


我要投稿

姓名

文章链接

提交即表示你已阅读并同意《个人信息保护声明》

专属顾问 专属顾问
扫码咨询您的优网专属顾问!
专属顾问
马上咨询
联系专属顾问
联系专属顾问
联系专属顾问
扫一扫马上咨询
扫一扫马上咨询

扫一扫马上咨询

和我们在线交谈!