【前端笔记】DIY轮播图的一些思考

1,035 阅读6分钟

最近需求 DIY 了一个移动端首屏的轮播图,从原生设计轮播图到实现细节,有了一些不成熟的想法,在这里和大家分享下。

DEMO地址在文章尾部,需要的自取。

产品需求

实现一个首页轮播 Banner,支持12个产品的自动滚动播放,在 App 内 Hybrid H5 的活动首页位置放置。

技术关键细节

1、在动画执行过程中,产品展示组建进行整体缩小和平移。

2、支持自动匀速滚动

3、支持手动滑动滚动

4、支持手机App的顺利展示

5、滚动过程中,产品展示组件间隔不变,商品图底部始终保持对齐

6、能够停到指定位置,即第几个产品在最中间

7、一个屏幕可以容纳一个100%、2个 172 / 212、2个 144 / 212(展示一般)比例产品卡

我先放上我实现 Demo,一些细节并没有按照UI的精细程度来做,避免一些保密风险。

1602143798_90_w300_h208.gif

设计思路

 首先将对于传统设计来说,还是依赖于 "translate" 的动画结合数据驱动的思路来进行设计,首先给大家介绍下我找到的最接近产品设计的原型【横向循环焦点图片展示】。

1600774229_96_w2853_h1184.gif

很明显上述的功能设计并不能直接使用,无法满足部分需求细节,例如间距不变,自定义底边对齐等,因此想DIY开发一个组件。

经过一次 Demo 验证开发,大体思路如下:

1、首先分为展示层和滚动层,展示层主要是主要负责UI展示,滚动层是负责数据 Scheme 的处理借鉴数据可视化的设计思路,有多少个产品数据,则在滚动层放置同样数量的空白元素(空白元素的高宽均是产品组件的最大时尺寸),用于匀速滚动模拟。这样做的好处是将逻辑控制和展示进行分离。

2、根据滚动层的滚动距离,计算出最靠近 view port 中线(手机屏幕中线)位置的产品卡索引和相对 中线 的相对位置,然后以此为开始向左右两侧进行排布。

3、每个产品组件的大小都是以距离 中线 的水平距离来决定的,距离越远则相对越小,其关系图如下:

image.png

4、产品卡部分,为了保持产品图片底部对齐,将底部产品 logo 部分通过 "position: absolute" 进行定位,整个产品卡底部对齐即可

以上则是设计思路,细节会在接下来的部分进行详细介绍。

实现细节

进入正题,整体功能代码比较多,这里就依据代码将具体细节和大家论述下。

滚动层实现

滚动层主要是承载逻辑计算和匀速滚动的模拟,放置了等同数量的空白站位元素,这里大小取 212px (极限大小),方便计算。通过在外层监听滚动事件、进行滚动模拟。当组件的滚动距离 scrollX 变化时,计算出所有产品卡的大小和位置信息。

       <div className="brand-showcase-wrapper-content"
          id="scrollX" data-scroll={scrollX}>
          {/* 滚动层 */}
          {data.map(index =>
            <div key={index} className="item-place-holder" />
          )}
          {/* 展示层 从占位的宽度,逐渐按比例缩小 */}
          {data.map((item, index) =>
            (<div className="item-wapper-layout"
              key={index}
              style={{
                width: layouts[index].width + 'px',
                height: layouts[index].width + 'px',
                left: layouts[index].position.left + 'px',
                right: layouts[index].position.right + 'px'
              }}
            >
              <Item data={item} scalStyle={layouts[index].ratio} />
            </div>)
          )}

        </div>

展示层实现

留意上面的代码你会发现,展示层所需要的数据如下图所展示。

   {
      width: 212,
      ratio: 1,
      position: {
        left: 375 - 212 / 2,
        right: 375 - 212 / 2,
      },
    };

 width 是产品组件的大小;
ratio 是产品组件内部元素的缩放比例;
position 是产品组件的 css position 的属性值。

如何计算得这些数据?

核心在 “getLayoutByScrollX” 的方法中,其思路是首先定位到最靠近屏幕中线的产品组件,然后确定其大小和位置信息,然后依次向左右重复相同的操作,这其中存在很多临界判定和位置计算,有点枯燥,这里就不详细介绍了,有兴趣的可以在代码中检索这个方法。

动画控制实现

动画本质就是一帧帧画面以一定顺序进行播放,下面则是一组《启动时滚动到底部动画》的实现,生产环境中由于是长时间运行请务必注意内存泄漏、屏幕切换、后台运行、预加载等考量,保证其动画的流畅和安全,不要直接使用下面的方式。

  useEffect(() => {
    timer = setInterval(() => {
      const currScrollX = parseInt(document.getElementById('scrollX').dataset.scroll, 10);
      if (currScrollX >= maxScrollX || currScrollX <= minScrollX) {
        clearInterval(timer);
      }

      for (let index = 0; index < 10; index ++) {
        setTimeout(() => {
          const nextScrollX = currScrollX + (index + 1) * 0.1 * UnitWidth;
          setScrollX(nextScrollX < minScrollX ? minScrollX : nextScrollX > maxScrollX ? maxScrollX : nextScrollX);
        }, index * 25);
      }
    }, 3000);
    return () => {
      clearInterval(timer);
    }
  })

疑问点

1、元素的排布为何不是从头开始排列,而是先定位最靠近中线的元素,然后以此为基础左右排布呢?

回答:如果从头排布的话,本身大小的会影响它距离屏幕中线的具体,相当于它变成一个二元一次方程求解计算,这样的话计算会更加复杂,性能消耗也更大。

2、为何不采用 css translate + scale 的方式来实现?现在通过绝对布局来模拟实现动画,性能方面是不是有缺陷?

回答:首先回答第一个问题,起始我更倾向于css translate + scale方案,但是遇到了一些知识盲区。
其一: 不太了解的CSS动画的协同工作机制,无法做到在滚动同时保持商品卡间距不变;
其二: 诸多组件动画同时启动和停止协同设计遇到了障碍。
从方案设计角度来说,上述的方案肯定是最优解,但是实现需要更加深入知识学习和设计模式的调整,本人后续也会持续优化的。 从成本角度来看,使用 position 来模拟确实更快,当然使用position定位来模拟动画确实是性能消耗很大,这也是js动画的通病,但是可以通过细心的微调可以提升一部分的性能,满足产品需求。

3、在手机端使用,那么它的适配是如何做的?

回答:这里为了一些保密需要就没有直接迁移完整代码,而是在很久之前的工程里在调研期间简单实现了下。
实际这里还是使用 rem 那一套方式来进行做的,其中在实现模拟滚动的时候,其速度的计算需要获取屏幕宽度和像素点密度来进行调整。

4、关于代码质量方面的一些情况介绍

这次Demo并没有引入完善的ESLINT,TYPESCRIPT,代码也粗糙,仅做一个验证思考的demo。无论在个人项目中还是工作项目中,尽可能保持良好的代码规范,受益你我他。

总结

首先这并不是一个完美无缺的解决方案,其性能存在一定的缺陷,需要客户端为 webview 提供足够的计算性能和资源缓存工作,尽可能提高其性能和流畅度。我相信在前端领域权威总是被挑战的,总是有更加优秀的方案产生,我也会一直探索下去,如果你有好的想法也请在留言里讨论。

DEMO github地址 首页轮播图-React,运行路径:/personal/branchShowCase。