【问题标题】:CSS Position Sticky weird behvaiour when scale applied to parent, sticky scrolls and different speed to element当缩放应用于父级、粘性滚动和不同的元素速度时,CSS Position Sticky 奇怪的行为
【发布时间】:2022-02-01 22:12:39
【问题描述】:

我不敢相信以前没有人注意到这一点,但我似乎找不到任何人注意到这种行为。我应该首先说这个 html 是很久以前写的(不是我写的),目前无法真正修改。

那么问题来了:

我们的 html 结构是这样布局的

.rptDisplay {
  overflow: auto;
  position: relative;
  top: 0;
  left: 0;
  z-index: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 200px;
  max-height: 100vh;
  font-size: 11px;
  text-align: left;
}

.rptPositioner {
  width: 33%;
  display: block;
  transform-origin: 0px 0px;
  transform: scale(3, 3);
}

.rptHeader {
  position: sticky !important;
  top: 0 !important;
  z-index: 1;
  background: #eee;
}

body {
  margin: 0;
  padding: 0;
}
<div class="rptDisplay">
  <div class="rptPositioner">
    <div class="rptHeader">Header</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
  </div>
</div>

问题是有一些非常奇怪的行为,当我滚动页眉时,页面向下滚动的速度与页面自身向下滚动的速度不同。请记住,用户可以更改变换比例以进行缩放,最近添加了粘性位置。

我将问题归结为缩放未应用到 rptDisplay 而是应用到 display 的子级但粘性元素的父级。如果我将缩放应用到显示器或标题,问题就会消失,但目前这不是一个选项。

我附上了一个 CodePen 来演示我们看到的问题。

https://codepen.io/steves165/pen/QWOLMwr

【问题讨论】:

标签: css positioning sticky


【解决方案1】:

一种解决方案是根据当前的变换比例值自行设置top

您已经提到,“用户可以更改变换比例以进行缩放。”这意味着正在使用 javascript。所以我们可以添加以下小代码来自己设置top

let scale = zoom.value;
let disp = document.querySelector('.rptDisplay');
let psnr = document.querySelector('.rptPositioner');
let header = document.querySelector('.rptHeader');

disp.addEventListener('scroll', setTop);

function setTop() {
  let offset = disp.scrollTop - (disp.scrollTop / scale);
  header.style.top = `${-offset}px`;
}

// for the demo set custom zoom
function adjust(r) {
  scale = r.value;
  psnr.style.transform = `scale(${scale},${scale})`;
  setTop()
}
.rptDisplay {
  overflow: auto;
  position: relative;
  top: 0;
  left: 0;
  z-index: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 155px;
  max-height: 100vh;
  font-size: 11px;
  text-align: left;
}

.rptPositioner {
  width: 33%;
  display: block;
  transform-origin: 0px 0px;
  transform: scale(3, 3);
}

.rptHeader {
  position: sticky !important;
  /* top: 0 !important;*/
  z-index: 1;
  background: rgb(215, 253, 199);
}

body {
  margin: 0;
  padding: 0;
}
<label>Set Zoom(1-4):
    <input type="range" id="zoom" min="1" max="4" value="3" onchange="adjust(this)">
  </label><br>
<hr>
<div class="rptDisplay">
  <div class="rptPositioner">
    <div class="rptHeader">Header</div>
    <div class="row">Row first</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row Last</div>
  </div>
</div>

【讨论】:

  • 这个例子非常简单通用,但它有一个副作用。由于不断的重新定位,滚动时会有滞后的抖动效果。这在移动设备上尤其明显,滚动快速流畅。不幸的是,只有在滚动时避免重新计算才能避免这个问题,就像 in this example 所做的那样。
  • 发生这种情况是因为每次像素更改都不会触发滚动事件。当您快速滚动浏览器时,会限制该事件,即它不会经常触发。
【解决方案2】:

我将添加一个解决此问题的示例。本例使用如下自定义CSS Variables(可自行设置):

  • --scale - 设置列表的缩放级别(而不是 transform: scale())。通过更改此 CSS 变量(直接、在 CSS 规则中或通过 JS),比例也会发生变化。
  • --width--height - 值,我们使用 JS 为列表的每个元素设置,如果项目随后更改其大小,则使用 ResizeObserver 的变量将被更新。

示例的逻辑基于我们不增加整个列表,而是单独增加每个元素的事实。为此,向每个元素添加一个等于“(scale - 1) * height”的margin-bottom,这样就会出现一个空白空间,该空间将被transform: scale() 填充。与margin-right--width 的效果类似。

下面的例子:

// tracking changes in the size of elements
const resizeObserver = new ResizeObserver((entries) => {
  entries.forEach(({ target }) => {
    target.style.setProperty("--height", target.offsetHeight);
    target.style.setProperty("--width", target.offsetWidth);
  });
});

// initialization of basic parameters and observers
document.querySelectorAll(".rptPositioner > div").forEach((el) => {
  el.style.setProperty("--height", el.offsetHeight);
  el.style.setProperty("--width", el.offsetWidth);
  resizeObserver.observe(el);
});

// control of the zoom control
document.querySelector("#scale-control").onchange = function () {
  document
    .querySelector(".rptDisplay")
    .style.setProperty("--scale", this.value);
};
.rptDisplay {
  overflow: auto;
  position: relative;
  top: 0;
  left: 0;
  z-index: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 170px;
  max-height: 100vh;
  font-size: 11px;
  text-align: left;
}

.rptPositioner {
  width: calc(100% / var(--scale, 1));
}

.rptPositioner>div {
  transform-origin: 0px 0px;
  transform: scale(var(--scale, 1));
  margin-bottom: calc(1px * var(--height) * (var(--scale, 1) - 1));
  margin-right: calc(1px * var(--width) * (var(--scale, 1) - 1));
  min-width: 100%;
}

.rptHeader {
  position: sticky !important;
  top: 0px !important;
  z-index: 999;
  background: #eee;
}

body {
  margin: 0;
  padding: 0;
}
<label>
Scale: 
<input type="number" id="scale-control" min="0.5" step="0.5" value="3">
</label>

<div class="rptDisplay" style="--scale: 3;">
  <div class="rptPositioner">
    <div class="rptHeader">Header1 Header1 Header1 Header1 Header1 Header1</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="rptHeader">Header3 Header3 Header3 Header3 Header3 Header3</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
  </div>
</div>

为了清晰起见,如果您需要在 X 轴和 Y 轴上使用不同的刻度进行简化,那么您可以创建两个单独的变量,例如 --scale-x--scale-y 并单独使用它们。

需要注意的是列表元素本身没有缩放(在示例中为.rptPositioner),但是 首先 --scale 变量可用于此元素,因此您可以直接缩放 CSS 属性(例如:border-width: calc((var(--scale) - 1) * 1px),其次 - 也可以通过 ResizeObserver 监视列表元素以获取此参数(宽度、高度等)。

【讨论】:

  • +1。是的,CSS 中的计算比 JS 更新/更快。如果您仅缩放列表项而不是列表,那么它们将溢出。在您的解决方案中,如果您将边框放在 rptPositioner 上,您会注意到所有行都溢出了。
  • @onkarruikar,感谢您的关注。没错,列表本身不会缩放,因此行元素会与它重叠,但不会与容器元素.rptDisplay 重叠。但是,某些列表参数可以手动缩放,因为--scale 变量也可用于列表。我添加了这个澄清作为回应。我的示例非常简单,根据具体任务,需要对其进行扩展。
  • 是的,您需要根据--scale调整列表的边距或宽度。
  • @onkarruikar,考虑到您的建议,我对示例进行了一些更新,并且与您的示例一样,为了清晰起见,我添加了一个缩放控制表单。在示例中,可能会出现错误,例如“超出 ResizeObserver 循环限制”-here is an explanation of why this error can be ignored。对于最终版本,可能仍需要进行详细的 CSS 更正,具体视情况而定,但我希望这个示例可以作为基础对某人有所帮助。
【解决方案3】:

.rptDisplay {
  overflow: auto;
  position: relative;
  top: 0;
  left: 0;
  z-index: 0;
  margin: 0;
  padding: 0;
  width: 100%;
  height: 200px;
  max-height: 100vh;
  font-size: 11px;
  text-align: left;
}

.rptPositioner {
  width: 33%;
  display: block;
  transform-origin: 0px 0px;
  transform: scale(3, 3);
}

.rptHeader {
  position: sticky !important;
  top: 0 !important;
  z-index: 1;
  background: #eee;
  width: 33%;
  transform-origin: 0px 0px;
  transform: scale(3, 3);/*same style or zoom*/
}

body {
  margin: 0;
  padding: 0;
}
<div class="rptDisplay">
  <div class="rptHeader">Header</div><!-- took out of positioner -->
  <div class="rptPositioner">
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
    <div class="row">Row</div>
  </div>
</div>

想法

  • 简单rptHeaderrptPositioner中取出,并将其作为兄弟而不是孩子放置
  • 既然我们现在已经取出了,我们将相同的缩放应用到rptHeader

附言

看起来非常复杂的问题可能有类似的解决方案 simple html

【讨论】:

    【解决方案4】:

    如果您对应用比例和粘性标题感兴趣,您可以直接将其应用到rptDisplay,如下所示

    .rptDisplay {
      height: 400px;
      width: 300px;
      overflow: auto;
      position: relative;
      transform-origin: 0 0;
      transform: scale(2);
    }
    
    .rptHeader {
      position: sticky;
      top: 0;
      background: white;
    }
    <div class="rptDisplay">
      <div class="rptPositioner">
        <div class="rptHeader">header 1</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="rptHeader">header 2</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
      </div>
    </div>

    【讨论】:

      【解决方案5】:

      我会创建一个新的

      transform-origin 可以将滚动率提高到x3
      所以试试Zoomcaniuse zoom

      它是非标准的,最初仅在 Internet Explorer 中实现。尽管其他几个浏览器现在都支持缩放,但不建议将其用于生产网站。

      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      
      .rptDisplay {
        padding-top: 50px;/* <--- to show the stick effect*/
        position: relative;
        zoom: 3; /*but this has less browser suppoert*/
      }
      
      .rptHeader {
        position: sticky;
        top: 0;
        background: #eee;
      }
      <div class="rptDisplay">
        <div class="rptHeader">Header</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
        <div class="row">Row</div>
      </div>

      【讨论】:

      • 尝试在JS中增加高度也许可行
      猜你喜欢
      • 2021-05-23
      • 1970-01-01
      • 2019-09-16
      • 2019-11-13
      • 1970-01-01
      • 1970-01-01
      • 2020-12-18
      • 2021-11-13
      • 1970-01-01
      相关资源
      最近更新 更多