【问题标题】:Reverse slider when swiped down向下滑动时反转滑块
【发布时间】:2018-09-10 14:18:34
【问题描述】:

我关注this article 的垂直滑动卡片滑块。

这个问题有两个部分。

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 会迫使浏览器重新绘制,这会严重影响性能吗?但那不是虚拟列表吗?这种场景下的性价比是多少?

【问题讨论】:

  • 你的意思是,你想添加底部按钮,点击 div 应该到底部?
  • 没有。刷掉的卡片应该重新添加到滑块中。
  • 作者忘记在他们的onSwipe*()函数之后调用resetOverlays();。一旦添加了这些,卡片就会在堆后面重置。
  • 叠加是可选的。我不会添加那个。事实上,那是不必要的 DOM。但是,如果您正在谈论“重置”卡片,那么理想情况下应该在所有卡片都被刷掉时发生。或者可能是如果滑块在循环。我的主要焦点是在滑块中向后移动,因为我们只能向前移动。
  • 关于性能:对您不想显示的卡片使用visibility: hidden;display:none; css 属性。这将节省浏览器的渲染工作。不同之处在于,如果您通过可见性进行控制 - 浏览器将为元素保留空间,但不会渲染它。使用display:none 浏览器将完全跳过元素渲染

标签: javascript performance css css-transforms


【解决方案1】:

这不会是一个完整的答案,但鉴于没有其他人回应,我将尝试回答您问题的第 1 部分。请注意,这只是一个示例,可帮助您了解如何使卡片倒退,而不是生产就绪的解决方案。

要重新连接“顶部”按钮以向后移动,您只需进行以下更改:

  • onSwipeTop() 中将currentPosition = currentPosition + 1; 更改为currentPosition = currentPosition - 1;
  • 同样在onSwipeTop() 中将transformUi(0, -1000, 0, topObj); 更改为transformUi(0, 0, 0, topObj);。这会隐藏卡片向上的动画。
  • updateUi() 中将i &lt; (currentPosition + items) 更改为i &lt;= (currentPosition + items)。这修复了三张卡片中只有两张更新的错误。

现在尝试单击左或右几次,然后单击顶部几次。每次单击顶部时,您应该会看到一张卡片返回。

大概您会想要创建一个新按钮“底部”而不是重新连接“顶部”,并且您可能还想限制对 currentPosition 的更改,因此您不能超出第一张/最后一张卡片但这至少应该让你开始。

我希望这会有所帮助。

【讨论】:

  • 这确实有助于开始。作为一个建议,如果我必须让它在我们向下滑动时跟随手指/光标,我们将如何处理?现在它只是向下滑动并捕捉到视图中。例如,如果我们向上滑动,它实际上会跟随您的光标/手指的移动,并且在我们释放它时达到某个阈值后,它会向上滑动。因此,或者,当向下滑动时,我们可以让它跟随手指,使其部分地逐渐出现在视野中,并在一个阈值之后,捕捉到滑块?
【解决方案2】:

反转滑块

以下是对脚本如何工作的非常简短的说明,包括让它反转方向和“取消滑动”卡片的建议。反向方法的灵感来自 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 中每个元素的可见性;回流甚至更昂贵,因为必须重新计算布局。见What's the difference between reflow and repaint?

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

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

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

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

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

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

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

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

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

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


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

【讨论】:

  • 抱歉文字太长了,我开始写,停不下来。
猜你喜欢
  • 2022-01-10
  • 2018-06-03
  • 1970-01-01
  • 2013-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-13
  • 2022-01-15
相关资源
最近更新 更多