×

CSS 移动端 瀑布流 组件 多场景布局

打造全新移动端瀑布流组件:实现多场景布局的完美解决方案

元智汇电子 元智汇电子 发表于2023-12-06 11:49:22 浏览370 评论0

抢沙发发表评论

📔背景介绍:

在日常开发中,瀑布流是一个常见的场景,各种组件库提供了不同的解决方案。然而,这些方案往往局限于特定场景,并且存在一些潜在问题。鉴于这些挑战,我们特别设计与开发了一款瀑布流组件,旨在兼容多种场景,解决现有方案的种种问题。


📔多场景布局:

目前,我们的瀑布流组件支持三种主要布局方式:卡片流、固定式瀑布流、交错式瀑布流。

  • 🍉卡片流: 以下拉列表的形式呈现,专注于单个列表项,适用于转转的二级列表页入口。这种布局提供了清晰的阅读体验,使用户能够聚焦于单个内容项。

    image.png

  • 🍉固定式瀑布流: 图片区域大小高度保持不变,创造统一整齐的界面。主要用于频道页等场景,使整体页面看起来有序且不杂乱。这种布局方式提供了视觉上的整洁感。

    image.png

  • 🍉交错式瀑布流: 宽度相等、高度不定的元素组成参差不齐的多栏布局,呈现在转转的首页以及商详推荐页面。这种布局方式通过视觉上的错落感,为用户带来更富有趣味性的浏览体验。

    image.png

🍉优势与特点:

我们的瀑布流组件不仅兼容多种场景,还具有以下优势:

  • 灵活性: 可根据不同需求选择合适的布局方式,满足多样化的设计要求。

  • 性能优化: 经过精心设计和优化,确保在各种场景下都能保持流畅的性能表现。

  • 自定义配置: 提供丰富的配置选项,使开发者能够根据项目需求进行灵活定制。

通过这一全新的移动端瀑布流组件,我们为开发者提供了一个更为全面、稳定且高度可定制化的解决方案,助力打造出色的移动端应用体验。


🚀现有方案的挑战

在前述三种场景中,第一种和第二种场景中图片高度固定,实现较为简单,可直接采用无限加载List组件。然而,问题主要出现在第三种场景,即交错式瀑布流。在这种情况下,必须等待图片加载完成后获取其高度,然后添加到瀑布流的最低列。否则,可能影响最低列的计算,导致列高不一致的情况。

转转公司内部对交错式瀑布流的实现尝试了几种方案:

🥂方案1:左右两栏布局,通过均分第一页瀑布流数据进行渲染。在第二页数据渲染时,采用IntersectionObserve监听元素,当元素出现在视窗内时,从数据源中取出下一个数据并添加到新的最低渲染列。

优点:采用IntersectionObserve实现懒加载,逻辑简单。

缺点:

  • 仅支持两栏布局,不支持参数配置多列;

  • 第一页数据不符合瀑布流规范,可能出现一列长一列短的情况;

  • IntersectionObserve的兼容性问题;

  • 缺乏数据加载完毕的事件,可能导致在无限加载组件中下拉请求两次接口。

🥂方案2:采用宽度百分比进行样式布局,首屏渲染时启用IntersectionObserve监听元素。元素出现在视窗后,通过setTimeout加载下一个瀑布流元素,并在该DOM上添加属性标识,防止二次触发。

优点:支持参数配置多栏布局,首屏符合瀑布流规范,提供了瀑布流加载完毕后的事件。

缺点:

  • 仍存在IntersectionObserve的兼容性问题;

  • 内部DOM查询、操作频率较高,维护成本较高;

  • 与无限加载List的逻辑耦合,复杂度较高;

  • setTimeout无法确保图片按照正确时序加载,可能导致获取最低列时不够准确。

🥂方案3:使用绝对定位布局,每个子组件waterfall-item内部新建一个image对象,监听onload事件后触发父组件waterfall进行瀑布流的重排。

优点:逻辑简单,易于维护,符合瀑布流规范,提供了常用配置项,完全加载后触发事件通知外部组件。

缺点:

  • 不支持图片懒加载;

  • 重绘次数过多,对性能影响较大;

  • 触发重绘的时机并非最精确,通过new image后的onload事件触发,而不是在当前image上绑定onload事件。

这些方案各自有其优劣,选择最适合项目需求的实现方式需综合考虑性能、兼容性和维护成本。


继续探索开源方案,我查阅了GitHub上星标数排行前四的瀑布流解决方案,以下是它们的简要概述:

🌰1. vue-waterfall:

  • 优点: 星标数最多的方案。

  • 缺点: 在组件渲染前需要知道图片的宽度和高度,这在接口返回中通常并不包含。

🌰2. vue-waterfall-easy:

  • 优点: 无需提前获取图片宽高信息,采用图片预加载后再进行排版。

  • 缺点: 耦合下拉和无限加载组件;包含PC端逻辑,包体积较大,对性能要求较高的页面可能不友好;一次加载所有图片,不支持懒加载。

🌰3. vue-waterfall2:

  • 优点: 支持高度自适应,同时支持懒加载。

  • 缺点: 内部多次创建image对象,伴随大量计算和滚动监听,可能对性能产生影响。

🌰4. vue2-waterfall:

  • 优点: 通过对masonry-layout和imagesloaded两个开源方案的封装实现,逻辑简单明了。

  • 缺点: 不支持懒加载。

综上所述,各方案各有利弊。选择适合项目的解决方案时,需根据具体需求和性能要求综合考虑。以下是一张简要总结图:

image.png

这些方案涵盖了不同的功能和特性,适用于各种场景。选择最合适的方案应根据项目具体情况,平衡性能、体积和功能需求。


📱设计全新的移动端瀑布流组件

当前市面上尚缺乏一款简单易用的移动端瀑布流组件,因此,我们计划整合现有的方案,重新打造一个具备以下优势的全新瀑布流组件:

  • 简单的CSS布局: 采用简洁的CSS布局方案,降低实现的复杂性。

  • 精简逻辑层面的实现: 精简逻辑,使组件易于理解和维护。

  • 支持高度自适应: 通过灵活的布局方案,实现高度的自适应,确保页面呈现更为流畅。

  • 支持懒加载: 集成懒加载机制,提升页面加载性能,仅在需要时加载图片。

🍚布局方案

了解到瀑布流的CSS布局方案主要有三种:

  • 绝对定位: 适用于PC端瀑布流,已在方案3和开源方案vue-waterfall-easy中采用。

  • 宽度百分比: 已在方案2和开源方案vue-waterfall2中使用,但可能存在精度问题。

  • Flex布局: 一些大型电商网站如蘑菇街采用这种布局,具备较好的兼容性和适应性。因此,新的瀑布流组件将选择Flex布局作为主要的布局方案,以达到最佳的移动端适配效果。

🍚瀑布流逻辑实现

在瀑布流的逻辑实现上,主要有三种方案:

  • 新建image对象: 在图片加载前获取图片原始宽高,根据瀑布流分配的宽度计算实际渲染高度,挂载到DOM上。然而,这种方式需要进行高度换算,同时不支持图片懒加载。

  • 拼接图片宽高信息: 在接口返回的图片URL中拼接图片的宽高信息,提前布局。这种方式支持懒加载,但需要进行一些后端和前端的改造。

  • IntersectionObserver懒加载: 通过IntersectionObserver监听图片元素,出现在视图中时从瀑布流数据队列的列头中取出一个数据并渲染到当前瀑布流的最低列。这种方式支持懒加载,不需要其他改造,是目前最适合的方案。因此,新的瀑布流组件将采用IntersectionObserver来实现懒加载。

通过这一全新设计的移动端瀑布流组件,我们将提供更加简单、灵活、高性能的解决方案,以满足多样化的业务需求。


🍚实现新瀑布流组件

面对新瀑布流组件的实现,我们首先需要解决IntersectionObserver的兼容性问题。尽管IntersectionObserver在解决传统滚动监听性能问题上表现出色,但其兼容性一直未得到全面支持,尤其在iOS平台上存在一些不完美之处。

image.png

为了解决这个问题,官方提供了一个polyfill。然而,由于polyfill的体积相对较大,直接引入可能不符合对性能极致追求的页面的友好性。因此,我们采用了一种动态引入polyfill的策略。

image.png

为了避免对所有环境加载polyfill,我们使用了动态引入的方式。这样,只有在不支持IntersectionObserver的环境下才会加载这个polyfill。检测的方法借鉴自Vue lazyload,具体代码如下:

image.png

这段代码通过检测当前环境是否支持IntersectionObserver来确定是否需要加载polyfill。在不支持的情况下,我们动态引入polyfill,确保对于不同平台的兼容性。


🍚瀑布流图片加载时序优化

在处理瀑布流图片加载时,由于图片加载是异步的过程,保证加载时序对于避免出现列高差异和页面抖动至关重要。直接在IntersectionObserver的回调函数触发后启动下一张图片加载容易导致这些问题的发生,因为回调触发时,图片可能只加载了部分内容。方案1和方案2均存在这个问题。

为了解决这个问题,我们可以利用IntersectionObserver回调函数提供的IntersectionObserverEntry对象的属性。具体来说,我们关注以下属性:

  • time: 可见性发生变化的时间,高精度时间戳,单位为毫秒。

  • target: 被观察的目标元素,是一个DOM节点对象。

  • complete: 图片是否已经完全加载。

通过在target上绑定onload事件,我们可以在图片加载完成后执行下一次瀑布流数据的渲染,确保获取最低列时的准确性。

image.png

这段代码通过检查图片是否已经完全加载,然后执行相应的回调,以确保下一次渲染时获取的最低列高度准确无误。这种方式有效地解决了瀑布流图片加载时序的问题,提高了页面的视觉稳定性。


🚀IntersectionObserver避免二次触发问题

在使用IntersectionObserver监听目标元素时,我们通常会面临一个问题,即回调函数会在目标元素可见性发生变化时触发两次:一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

为了避免第二次触发监听逻辑,我们可以在第一次触发时停止观察目标元素。

image.png

通过上述代码,我们在第一次触发回调时执行done函数,该函数会停止对目标元素的观察,防止在回拉时再次触发监听逻辑。

🔒解决首屏渲染白屏问题的两种方案

在处理首屏渲染白屏问题时,我们提供了两种解决方案:

🌰方法一: 首屏渲染时,采用并行渲染的方式加载部分图片,然后在后续采用串行渲染的方式加载剩余图片。这种方式可以通过调整firstPageCount参数来灵活控制首屏加载的图片数量。

image.png

🌰方法二: 通过添加动画效果,从视觉上缓解白屏现象对用户体验的影响。组件内置了两个动画效果,可以通过animation参数进行配置。

通过采用这两种方案,我们有效地解决了首屏渲染白屏问题,提高了用户对页面的整体感知。


🧪懒加载时的白屏问题及解决方案

在采用懒加载方案时,用户在滚动浏览时可能会遇到下一张图片加载过慢导致的短暂白屏时间,影响用户体验。为解决这个问题,我们可以利用IntersectionObserver的rootMargin属性来提前加载后面的数据,防止用户滚动到底部时的白屏现象,并避免渲染过多影响性能。

image.png

通过设置rootMargin,我们可以合理地提前加载一定数量的数据,既提高用户浏览体验又不会过度渲染,影响性能。默认设置的400px大约是提前渲染半屏的数据,可以根据实际情况进行调整。


📖如何配合无限加载组件

通常情况下,我们会将无限加载和瀑布流的逻辑分开,因此在瀑布流数据渲染完后需要通知外部组件。否则,可能会出现瀑布流未渲染完就触发了无限加载逻辑,导致发送两次接口请求的问题。

为了避免这种情况,可以在进行瀑布流渲染的过程中增加一个判断,如果队列中没有数据了,就通知外部无限加载组件进行下一次请求。

image.png

通过上述判断,我们能够在确保瀑布流渲染完毕后通知外部组件,避免触发不必要的无限加载逻辑。


📝总结与源码

以上是在新瀑布流组件开发中遇到的一些问题及相应的解决方案。尽管这套方案还有待进一步优化,但已经在公司内部得到了应用。目前,该代码尚未开源,如果需要源码的话,可以关注公众号,并回复获取。

对于瀑布流组件,欢迎大家提出更好的意见和建议,也欢迎进行沟通和讨论。



群贤毕至

访客