关于 javascript:Position sticky: 可滚动,当长于视口时

Position sticky: scrollable, when longer than viewport

当带有 position: sticky 的元素"卡住"并且比视口长时,您只能在滚动到容器底部后才能看到其内容。

如果"卡住"元素随着文档滚动并在到达其底部边缘时停止,那将是很酷的。如果用户向后滚动,同样的事情会再次发生,但相反。

例子

TLDR;有一个库(StickyKit)可以满足我的要求,但在新的异步滚动中表现不佳。

带有 StickyKit 的 JSFiddle - https://jsfiddle.net/cibulka/4nd3b0tt/ - (这符合我的描述,但性能不佳,见下文)

JSFiddle 与原生 position: sticky - https://jsfiddle.net/cibulka/np6afe9c/1/ - https://jsfiddle.net/cibulka/pxz6e0qv/ - (不是)

背景

很长一段时间以来,我都是 StickyKit 的快乐用户。不幸的是,它不适用于异步滚动,越来越多的浏览器使用异步滚动来提高性能。例如,对于新的 Firefox Quantum (57),StickyKit 几乎无法使用。

我在 StickyKit Github 中创建了一个问题,但是包似乎被作者放弃了:https://github.com/leafo/sticky-kit/issues/252

正因为如此,我不得不弃用 StickyKit 并转向原生 position:sticky(用 StickyFill 填充)。不幸的是,有几件事 position:sticky 不能做,这就是其中之一。

position:sticky 还有另一个问题:Position sticky: overlay

我在寻找什么

基本上是关于如何解决此问题的建议。我准备使用不同的 JS/jQuery 库,编写自己的代码或使用一些古怪的 CSS hack 来破解 position:sticky 功能。


有一个名为 Sticky Sidebar 的库正是为解决该问题而设计的。


我已经接受了 jnns 的答案并对其进行了更新,以便它在滚动之间平滑,就像 Sticky Kit 一样。唯一的问题是它需要一个包含 div s.t. 的幻数。容器在 div 绝对定位时保持其大小 - 这可以通过 css 变量在您的代码中解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
window.onscroll = function (e) {
  if (window.scrollY < this.prevScrollY) {
    // Track position state of nav
    // 1 == stuck to top
    // 0 == absolute positioning
    // -1 == stuck to bottom
    this.stick_pos = scrollUpwards(this.stick_pos);
  } else {
    this.stick_pos = scrollDownwards(this.stick_pos);
  }
  this.prevScrollY = window.scrollY;
}

function scrollUpwards(stick_pos) {
  // If the element is already stuck to the top then we are fine
  if(stick_pos === 1) return stick_pos;
  // Figure out where the new window will be after scroll
  let aside = $("aside").get(0);
  let aboveAside = aside.getBoundingClientRect().top > 0;
  // If we are going above the element then we know we must stick
  // it to the top
  if (aboveAside){
    $("aside").css("position","sticky")
      .css("top", 0)
      .css("bottom", '')
      .css("align-self","flex-start");
    return 1;
  }
  // If it will still be below the top of the element, then we
  // must absolutely position it to its current position - if it already is absolutely positioned then we do nothing
  if (stick_pos == 0) return stick_pos;
 
  // Undo the stick to the bottom
  // First get the current position
  $("aside")
    .css("top", aside.offsetTop)
    .css("position","absolute")
    .css("bottom", '')
    .css("align-self","");
  return 0;
}

function scrollDownwards(stick_pos) {
  /*
  let aside = $("aside").get(0);
  let aboveAside = aside.offsetTop >= window.scrollY;
  let browser_bottom = window.scrollY + window.innerHeight;
  let aside_bottom = aside.offsetTop + aside.offsetHeight;
  let belowAside = browser_bottom >= aside_bottom;
  if (aboveAside) {
    //console.log("stick to bottom");
    $("aside").css("top", '');
    $("aside").css("bottom", 0);
    $("aside").css("align-self","flex-end");
  }
  */

  // If the element is already stuck to the bottom then we are fine
  if(stick_pos === -1) return stick_pos;
  // Figure out where the new window will be after scroll
  let aside = $("aside").get(0);
  let browser_bottom = window.innerHeight;
  let aside_bottom = aside.getBoundingClientRect().top + aside.offsetHeight;
  let belowAside = browser_bottom > aside_bottom;
  // If we are going below the element then we know we must stick
  // it to the bottom.
  if (belowAside){
    $("aside").css("position","sticky")
      .css("top", '')
      .css("bottom", 0)
      .css("align-self","flex-end");
    return -1;
  }
  // If it will still be above the bottom of the element, then we
  // must absolutely position it to its current position - if it already is absolutely positioned then we do nothing
  if (stick_pos == 0) return stick_pos;
 
  // Undo the stick to the top
  // First get the current position
  // $("aside").css("position","absolute")
  // .css("top", aside.offsetTop);
  $("aside")
    .css("top", aside.offsetTop)
    .css("position","absolute")
    .css("bottom", '')
    .css("align-self","");
  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
div#section {
  /* begin: irrelevant styling */
  margin: 5em auto;
  padding: 0.625rem;
  max-width: 300px;
  font-family: sans-serif;
  font-size: 18px;
  line-height: 1.5em;
  text-align: justify;
  background-color: #dbe4ee;
  /* end: irrelevant styling */
  display: flex;
  justify-content: space-around;
}
div#section div#nav-container {
  position: relative;
  display: flex;
  min-width: 2em;
}
div#section div#nav-container aside {
  position: sticky;
  align-self: flex-start;
  /* begin: irrelevant styling */
  background-color: #81a4cd;
  color: white;
  text-align: center;
  width: 2em;
}
div#section div#nav-container aside div {
  padding: 0 .3em;
}
div#section article {
  margin-left: 0.5em;
}
div#section article p {
  margin: 0;
}
div#section article p + p {
  margin-top: 1.5em;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js">

 
 
    A
    B
    C
    D
    E
    F
    G
    H
    I
    J
    K
    L
    M
    N
    O
    P
    Q
    R
    S
    T
    U
    V
    W
    X
    Y
    Z
  </aside>
 
 
    <p>Perferendis ut iusto voluptatem ex temporibus aut autem amet. Sit vero in soluta. Est officia asperiores tenetur vel quam nostrum eum facere. Sed totam quasi libero at facilis doloremque. Non aut velit odio. Tempora dolore sunt recusandae sed quia
      sunt.</p>

    <p>Voluptatem optio asperiores dolorem voluptatem. Ipsa alias perspiciatis doloribus est nisi ut. Fuga aut et vitae consequatur dolor corrupti aut minima.</p>

    <p>Facilis et ut eligendi. Excepturi labore asperiores vero. Perferendis porro sunt molestiae. In sit dolorem eum esse sit inventore est. Atque perspiciatis commodi nihil.</p>

    <p>Consequatur ipsa id repellendus voluptatem perspiciatis temporibus. Praesentium eveniet nemo laudantium inventore similique impedit nihil esse. Maiores iste commodi molestiae quas odit nihil ex corrupti. Illum id amet non vero.</p>

    <p>Voluptas soluta itaque et. Aperiam quasi sint eos ullam. Assumenda facilis omnis alias numquam. Odio quia esse vel et minima soluta architecto. Qui saepe consequatur aut rerum. Et et aut voluptatibus inventore.</p>

    <p>Perferendis ut iusto voluptatem ex temporibus aut autem amet. Sit vero in soluta. Est officia asperiores tenetur vel quam nostrum eum facere. Sed totam quasi libero at facilis doloremque. Non aut velit odio. Tempora dolore sunt recusandae sed quia sunt.</p>

    <p>Voluptatem optio asperiores dolorem voluptatem. Ipsa alias perspiciatis doloribus est nisi ut. Fuga aut et vitae consequatur dolor corrupti aut minima.</p>

    <p>Facilis et ut eligendi. Excepturi labore asperiores vero. Perferendis porro sunt molestiae. In sit dolorem eum esse sit inventore est. Atque perspiciatis commodi nihil.</p>

    <p>Consequatur ipsa id repellendus voluptatem perspiciatis temporibus. Praesentium eveniet nemo laudantium inventore similique impedit nihil esse. Maiores iste commodi molestiae quas odit nihil ex corrupti. Illum id amet non vero.</p>

    <p>Voluptas soluta itaque et. Aperiam quasi sint eos ullam. Assumenda facilis omnis alias numquam. Odio quia esse vel et minima soluta architecto. Qui saepe consequatur aut rerum. Et et aut voluptatibus inventore.</p>


您可以尝试根据滚动方向使用jQuery从上到下切换粘性元素的锚点和位置,并且仍然使用CSS的原生position: sticky

我让它在这个 codepen 中工作,但没有时间在方向改变时消除跳跃。但也许这对你来说已经足够了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Use display: flex on the container

window.onscroll = function (e) {
  if (window.scrollY < this.prevScrollY) {
    scrollUpwards();
  } else {
    scrollDownwards();
  }
  this.prevScrollY = window.scrollY;
}

function scrollUpwards() {
  $("aside").css("top", 0);
  $("aside").css("bottom", '');
  $("aside").css("align-self","flex-start");

}

function scrollDownwards() {
  $("aside").css("top", '');
  $("aside").css("bottom", 0);
  $("aside").css("align-self","flex-end");
}

你看过 Scrollama

它使用新的 Intersection Observer Web API,它基本上是当某个元素出现在视口中时,浏览器会告诉 JS,而 JS 不必监听滚动事件。因此,对于以高性能的方式在 JS 中实现类似 position: sticky 的行为很有用。