首页 > 解决方案 > 向下滑动时反转滑块

问题描述

我按照这篇文章了解了一个垂直的可刷卡滑块。

这个问题有两个部分。

1. 下滑时如何反转滑块的方向,我看不懂?

这是相关的codepen - https://codepen.io/bmarcelino/pen/vRYPXV

更新卡片的相关功能

function updateUi() {
    requestAnimationFrame(function(){
        elTrans = 0;
        var elZindex = 5;
        var elScale = 1;
        var elOpac = 1;
        var elTransTop = items;
        var elTransInc = elementsMargin;

        for(i = currentPosition; i < (currentPosition + items); i++){
            if(listElNodesObj[i]){
                listElNodesObj[i].classList.add('stackedcards-bottom', 'stackedcards--animatable', 'stackedcards-origin-bottom');

                listElNodesObj[i].style.transform ='scale(' + elScale + ') translateX(0) translateY(' + (elTrans - elTransInc) + 'px) translateZ(0)';
                listElNodesObj[i].style.webkitTransform ='scale(' + elScale + ') translateX(0) translateY(' + (elTrans - elTransInc) + 'px) translateZ(0)';
                listElNodesObj[i].style.opacity = elOpac;
                listElNodesObj[i].style.display = 'block';
                listElNodesObj[i].style.zIndex = elZindex;

                elScale = elScale - 0.04;
                elOpac = elOpac - (1 / items);
                elZindex--;
            }
        }

    });

};

我不是特别精通Javascript。
到目前为止,滑动时滑块仅向一个方向移动 - 向前。我希望了解将向后移动添加到滑块的实现。

2.关于性能

此外,requestAnimationFrame确实有助于在滑动时提供流畅的体验。但是 DOM 中应该有多少张卡片有限制吗?我将调用 API 服务来获取内容,因为它将返回媒体,那么简单地将不透明度设置为 0 是否有助于以任何方式减少内存使用?

作者认为删除 DOM 会迫使浏览器重新绘制,这会严重影响性能吗?但那不是虚拟列表吗?这种场景下的性价比是多少?

标签: javascriptperformancecsscss-transforms

解决方案


反转滑块

以下是脚本如何工作的非常简短的解释,包括让它反转方向和“取消滑动”卡片的建议。反向方法的灵感来自 Rocky 的出色回答 - 他的想法值得充分肯定。

  1. 加载文档后,脚本会获取所有可用卡片的列表。在您的示例中,卡片是 DOM 中的硬编码元素,卡片listElNodesObj列表 是这些元素的列表。记住这一点很重要:卡片不是抽象的,它们基本上是页面中的元素。当您将媒体和数据添加到您的卡片时,您必须将其附加到 DOM 中的元素(例如,使用数据属性)。

  2. 该脚本获取当前的卡片索引,称为currentPosition; 首先是这张牌。然后它显示当前卡片和它后面的两张卡片(currentPosition + 1currentPosition + 2)。

  3. 在输入时,卡片会被适当地动画以飞离左侧、右侧或顶部。当前卡片索引加一,在堆栈中前进一。显示新的当前卡片及其后面的两张卡片。

目前所有动作——向左、向右和向上滑动——都进入堆栈。要反转方向,您需要侦听新操作(或重新调整当前操作,例如向上滑动),并且在该操作上,当前卡片索引减 1。添加小于零的检查。currentPosition = Math.max( 0, currentPosition - 1 );

Rocky 提供了一个很好的解决方案来实现这一点。

现在这个实现从 DOM 中已经存在的所有卡片开始。您似乎想从后端 API 更新您的卡片堆栈。为此,您需要一种将卡从一端弹出并在刷卡时在另一端添加新卡的方法。如上所述,您的卡片列表与 DOM 紧密相关,因此您需要对其进行一些抽象来实现这一点。创建一个您从 API 填充的元素列表,并用它填充您的初始文档(而不是相反)。当您滑动时,无论是向前还是向后,从后退端弹出一个元素,并将一个从您的 API 填充的新元素添加到前进端。有趣的是,您的列表大小当前位置都将始终保持不变。

如果您当前的位置始终与最上面的卡片相距一格,并且堆栈中的最后一张卡片比最后一张可见卡片多一张,那么您将始终有一张卡片可以动画显示。

表现

在这里稍微澄清一下术语:改变类似的东西opacity会导致重绘,从 DOM 中删除元素,无论是软删除还是硬删除,都会导致重排。重绘成本很高,因为浏览器必须检查 DOM 中每个元素的可见性;回流甚至更昂贵,因为必须重新计算布局。请参阅回流和重绘之间有什么区别?

有两种方法可以限制 DOM 中的卡片数量。您可以设置将display: none其留在内存和 DOM 中,但会阻止浏览器在重排或重绘时考虑它。或者,您可以使用parent.appendChild(child)添加卡片和parent.removeChild(child)移除卡片,确保一旦移除该元素,JavaScript 中就不存在对该元素的引用,并且一旦垃圾收集器运行,移除的元素将从内存中物理移除。两者都会触发回流。元素opacity: 0将完全保留在 DOM 中以进行重排和重绘。

至于什么可以提供最佳性能:更改不透明度或从内存中删除,这实际上取决于您的实现。不过,我可以给你一些相关的指示。

内存限制DOM 中应该有多少卡有限制吗? ”当然,但这取决于您的数据。如果您的卡片总数非常少,您确实可以在开始时将它们全部加载并使用opacity: 0或隐藏刷过的卡片display: none。动画的流动性甚至可能会得到改善(请参阅下面的动画阻塞计算点)。纯粹来自更高内存使用的性能差异几乎肯定不会被注意到,因为现代浏览器有大量内存,并且会在需要页面文件或交换之前停止您的脚本。如果您的内存中确实有如此巨大的 DOM,性能会明显下降,那么您的内容下载时间将是一个更大的问题。

但是,更重要的是,您正确地询问删除或添加元素是否是虚拟列表的全部要点。如果您将永远不会再次访问它,为什么要将元素保留在内存中,或者为什么要加载一个在列表中很远的元素,它可能永远无法到达。事实上,您声明您将从 API 访问内容,这强烈暗示您将一次访问卡片内容。等到您从 API 中获得所有内容可能需要很长的时间;正如您似乎已经意识到的那样,仅访问您需要填写固定大小列表的卡片会提供更好的体验。(如果您打算反转滑块的方向,那么您应该在内存中至少保留一张刷过的卡,以便在向后滑动时不会

动画阻塞计算除了下载时间之外,对于被刷卡或在列表中太靠后的卡片来说,真正的性能优势在于它们的内容已经存在于 DOM 中display: noneopacity: 0(如上所述,opacity: 0还有一个优势:它不会触发回流)。相比之下,从 DOM 中物理添加和删除元素需要额外的计算,即在 DOM 树中插入或删除卡片节点及其所有子节点。如果这是同步完成的,那么您将有一个动画阻塞计算,因此滑动动画在 DOM 树更新完成之前无法发生。

但是,让我们保持透视。首先,从 DOM 树中添加和删除节点通常非常快。innerHTML 已被证明在添加到 DOM 时稍微快一些,但在删除时要慢得多,因此请谨慎选择毒药。但除非您向卡中添加数十或数百千字节的数据,否则该操作可能需要不到一毫秒的时间. 其次,您声明您将从 API 检索列表的内容,这意味着异步连接。如果您在构建异步函数时小心,那么向 DOM 添加内容的函数不一定会干扰动画。(并不是暗示 JavaScript 是多线程的;它是单线程但构造良好的异步代码意味着函数执行的顺序无关紧要)。如果在动画未排队时添加或移除卡片,则任何性能影响都将变得更短且更不易察觉

最后,每个 DOM 操作都是一次新的渲染更新,因此您希望尽可能少地进行操作。因此,您将在内存中创建相互对抗的元素,并且仅在最后将最高元素插入 DOM。请参阅最快的 DOM 插入。如果您可以在该卡片中包含所有卡片数据,那么每次滑动只需要两次 DOM 操作。这取决于卡片包含的媒体,但可以想象,在最坏的情况下,向堆栈添加和移除卡片仅需要数十毫秒的组合时间。

GPU大多数渲染引擎都可以使用 GPU,在涉及大量像素的绘图和合成操作中,它可以实现比 CPU 更好的效率。默认情况下,渲染层不使用 GPU 进行渲染。Chrome状态下的页面GPU 加速合成,

虽然理论上每个单独的 RenderLayer 都可以将自己绘制到单独的支持表面 [即 GPU 可访问的合成层],但实际上这在内存(尤其是 VRAM)方面可能非常浪费。

为确保使用 GPU 渲染诸如不透明度变化之类的动画,您需要以隐式合成的方式访问动画. 上面的页面有一个如何做到这一点的完整列表,但基本上你会使用 CSS 动画来改变不透明度,这会提示浏览器将元素提升为合成层。您提供的当前代码使用 JavaScript 更新每个动画帧的不透明度,因此不透明度更改不是隐式合成的候选者。(您似乎正在对触发隐式合成的运动动画使用 3D 变换,因此可能已经对 GPU 进行了优化)。更改代码以使用 CSS 动画并非易事,但它很可能会提高性能,特别是动画期间的帧速率。当然,需要对您的特定场景进行基准测试来验证这一点,请参阅https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/讨论为什么某些 GPU 动画可能会运行得更慢。


总而言之,由于从虚拟列表中动态添加和删除元素而带来的性能提升,虽然可能略高于完全加载整个列表,但每次滑动可能只是额外的几毫秒。使用异步实现,动画期间的帧速率不应改变。这通常应该是一个简单的让步,因为可能会节省大量初始下载时间,但必须与您的特定实现的其他细节一起考虑。


推荐阅读