当任何祖先处于位置时,3D 变换会闪烁:固定

The 3D transforms flickering when any ancestor is position: fixed

提问人:marsielko 提问时间:9/12/2023 更新时间:9/12/2023 访问量:50

问:

我已经实现了基于 3d 变换的自定义滚动条:https://developer.chrome.com/blog/custom-scrollbar/,但是我注意到滚动条拖动器在滚动过程中的闪烁行为。

flickering scrollbar's dragger

此行为仅发生在 Mozzila 浏览器中 - 测试于:“Mozilla/5.0 (X11;Ubuntu的;Linux x86_64;rv:109.0) Gecko/20100101 Firefox/117.0”。为了使滚动条拖动器动画流畅,我必须删除“position: fixed”,这是我在当前用例中根本无法完成的步骤。

检查下面的代码片段(或打开此 codepen 项目:https://codepen.io/marsielko/pen/OJrmoWq)并在 html/body 元素中切换“position: fixed”,并上下滚动鼠标滚轮。

// Taken from: https://github.com/GoogleChromeLabs/ui-element-samples/blob/gh-pages/custom-scrollbar/scrollbar.js

(function(scope) {
  var dragging = false;
  var lastY = 0;

  function dragStart(event) {
    dragging = true;
    this.style.pointerEvents = 'none';
    this.style.userSelect = 'none';

    lastY = (event.clientY || event.clientY === 0) ? event.clientY : event.touches[0].clientY;
  }

  function dragMove(event) {
    if (!dragging) return;
    var clientY = (event.clientY || event.clientY === 0) ? event.clientY : event.touches[0].clientY;
    this.scrollTop += (clientY - lastY)/this.thumb.scaling;
    lastY = clientY;
    event.preventDefault();
  }

  function dragEnd(event) {
    dragging = false;
    this.style.pointerEvents = 'initial';
    this.style.userSelect = 'initial';
  }


  // The point of this function is to update the thumb's geometry to reflect
  // the amount of overflow.
  function updateSize(scrollable) {
    scrollable.style.width = '';
    scrollable.style.width = `calc(${getComputedStyle(scrollable).width} + 200px)`;

    var thumb = scrollable.thumb;
    var viewport = scrollable.getBoundingClientRect();
    var scrollHeight = scrollable.scrollHeight;
    var maxScrollTop = scrollHeight - viewport.height;
    var thumbHeight = Math.pow(viewport.height, 2)/scrollHeight;
    var maxTopOffset = viewport.height - thumbHeight;

    thumb.scaling = maxTopOffset / maxScrollTop;
    thumb.style.height = `${thumbHeight}px`;

    if(scrollable.isIOS) {
      thumb.nextElementSibling.style.marginTop = `-${thumbHeight}px`;
      var z = 1 - 1/(1+thumb.scaling);
      thumb.style.transform = `
        translateZ(${z}px)
        scale(${1-z})
        translateX(-200px)
      `;
    } else {
      thumb.style.transform = `
         scale(${1/thumb.scaling})
         matrix3d(
           1, 0, 0, 0,
           0, 1, 0, 0,
           0, 0, 1, 0,
           0, 0, 0, -1
         )
         translateZ(${-2 + 1 - 1/thumb.scaling}px)
         translateX(-200px)
      `;
    }
  }

  function makeCustomScrollbar(scrollable) {
    // Edge requires a transform on the document body and a fixed position element
    // in order for it to properly render the parallax effect as you scroll.
    // See https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5084491/
    if (getComputedStyle(document.body).transform == 'none')
      document.body.style.transform = 'translateZ(0)';
    var fixedPos = document.createElement('div');
    fixedPos.style.position = 'fixed';
    fixedPos.style.top = '0';
    fixedPos.style.width = '1px';
    fixedPos.style.height = '1px';
    fixedPos.style.zIndex = 1;
    document.body.insertBefore(fixedPos, document.body.firstChild);

    scrollable.style.perspectiveOrigin = 'top right';
    scrollable.style.transformStyle = 'preserve-3d';
    scrollable.style.perspective = '1px';

    var perspectiveCtr = document.createElement('div');
    perspectiveCtr.style.perspectiveOrigin = 'top right';
    perspectiveCtr.style.transformStyle = 'preserve-3d';
    perspectiveCtr.style.width = '100%';

    perspectiveCtr.style.position = 'absolute';
    perspectiveCtr.style.pointerEvents = 'none';
    perspectiveCtr.classList.add('perspective-ctr')

    while(scrollable.firstChild) perspectiveCtr.appendChild(scrollable.firstChild);

    scrollable.insertBefore(perspectiveCtr, scrollable.firstChild);
    var thumb = document.createElement('div');
    thumb.classList.add('thumb');
    thumb.style.pointerEvents = 'initial';
    thumb.style.position = 'absolute';
    thumb.style.transformOrigin = 'top right';
    thumb.style.top = '0';
    thumb.style.right = '0';
    perspectiveCtr.insertBefore(thumb, perspectiveCtr.firstChild);
    scrollable.thumb = thumb;
    scrollable.perspectiveCtr = perspectiveCtr;

    // We are on Safari, where we need to use the sticky trick!
    if (getComputedStyle(scrollable).webkitOverflowScrolling) {
      scrollable.isIOS = true;
      thumb.style.right = '';
      thumb.style.left = '100%';
      thumb.style.position = '-webkit-sticky';
      perspectiveCtr.style.perspective = '1px';
      perspectiveCtr.style.height = '';
      perspectiveCtr.style.width = '';
      perspectiveCtr.style.position = '';
      Array.from(scrollable.children)
        .filter(function (e) {return e !== perspectiveCtr;})
        .forEach(function (e) {perspectiveCtr.appendChild(e);});
    }

    scrollable.thumb.addEventListener('mousedown', dragStart.bind(scrollable), {passive: true});
    window.addEventListener('mousemove', dragMove.bind(scrollable));
    window.addEventListener('mouseup', dragEnd.bind(scrollable), {passive: true});
    scrollable.thumb.addEventListener('touchstart', dragStart.bind(scrollable), {passive: true});
    window.addEventListener('touchmove', dragMove.bind(scrollable));
    window.addEventListener('touchend', dragEnd.bind(scrollable), {passive: true});

    var f = function () {
      updateSize(scrollable);
    };
    requestAnimationFrame(f);
    window.addEventListener('resize', f);
    return f;
  }

  window.addEventListener('load', function () {
    Array.from(document.querySelectorAll('.overflow')).forEach(scrollable => {
      makeCustomScrollbar(scrollable);
      updateSize(scrollable);
    });
  });

})(self);
/* Taken from: https://github.com/GoogleChromeLabs/ui-element-samples/blob/gh-pages/custom-scrollbar/index.html */
::-webkit-scrollbar {
  width: 0px;
  height: 0px;
}

html, body {
  margin: 0;
  border: 0;
  padding:0 ;
  height: 100%;
  overflow: hidden;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  position: fixed;
}

.overflow {
  overflow-x: hidden;
  overflow-y: scroll;
  background: #EDEDED;
  width: 100%;
  height: 100%;
  position: relative;
  -webkit-overflow-scrolling: touch;
}

.thumb {
  width: 68px;
/*   background-image: url('cat.gif'), linear-gradient(90deg, transparent, magenta, red, yellow, limegreen, turquoise, blue, magenta, transparent); */
/*   background-position: center bottom, center -40px; */
/*   background-repeat: no-repeat, no-repeat; */
/*   background-size: contain, contain; */
  background-color: red;
}

.fakecontent {
  padding: 25vh 0;
  border: 1px solid transparent;
}
<!-- Taken from: https://github.com/GoogleChromeLabs/ui-element-samples/blob/gh-pages/custom-scrollbar/index.html -->
<body>
  <main class="glider-scrollable overflow">
    <div class="fakecontent">Lorem</div>
    <div class="fakecontent">ipsum</div>
    <div class="fakecontent">dolor</div>
    <div class="fakecontent">sit</div>
    <div class="fakecontent">amet,</div>
    <div class="fakecontent">consectetur</div>
    <div class="fakecontent">adipiscing</div>
    <div class="fakecontent">elit</div>
  </main>
<body>

我的问题是,有没有办法像在 Chrome 中那样使拖拽器的动画流畅,或者这是一个错误,需要报告?

HTML CSS 滚动条 css-transforms

评论


答: 暂无答案