自定义滚动条拇指超出范围

Custom scrollbars thumb goes out of bounds

我正在尝试创建一个与默认滚动条(带动画)一起滚动的拇指滚动条。我终于能够实现它,唯一的问题是,自定义滚动条不会在确切位置滚动。如果一直向下滚动,则拇指滚动条会越过其父元素。

此外,拇指滚动条比默认滚动条大。这是数学:

scrollBarThumb.style.height = (innerWrapper.parentElement.offsetHeight * innerWrapper.parentElement.offsetHeight / innerWrapper.scrollHeight) + 'px';
scrollBarPosition = clamp(scrollBarPosition, 0, scrolledToBottom)
scrollBarPosition = scrollBar.offsetHeight * scrollBarPosition / innerWrapper.scrollHeight;

我使用的公式是自定义滚动条的公认公式。

两个问题是:(它们可能是一个问题,因为我认为如果高度固定,那么滚动位置也会自动固定。)首先,滚动条的拇指比默认的大。其次,当您一直向下滚动时,滚动条拇指会越过其父项。

我做错了什么,我该如何解决?

JSFiddle

console.clear();

var innerWrapper = document.getElementById('innerWrapper');
var scrollBar = document.getElementById('scrollbar');
var scrollBarThumb = scrollBar.firstElementChild

scrollBarThumb.style.height = (innerWrapper.parentElement.offsetHeight * innerWrapper.parentElement.offsetHeight / innerWrapper.scrollHeight) + 'px';

innerWrapper.addEventListener('mousewheel', handleScroll);
innerWrapper.addEventListener('DOMMouseScroll', handleScroll);

var duration = 35,
  scrollSpeed = 2,
  animateAmount = 30;

var scrolledToBottom = 0,
  scrollDirection = 0, // 1 = scroll down, -1 = scroll up
  animationID;




function handleScroll(e) {
  // Cancel previous animation
  cancelAnimationFrame(animationID);
  // Scroll faster
  scrollSpeed += 2;

  // Reason for negative `-.wheelDelta` because Firefox
  // return oposite value. See http://phrogz.net/js/wheeldelta.html
  // Get 1 or -1
  var delta = Math.max(-1, Math.min(1, (-e.wheelDelta || e.detail)));
  // Check if scroll direction changed
  if (scrollDirection != delta) {
    scrollSpeed = 2; // Start slowly - restart speed
    scrollDirection = delta;
  }
  var start = innerWrapper.parentElement.scrollTop,
    end = start + animateAmount * scrollSpeed * delta, // Where to end the scroll
    change = end - start, // base change in one scroll
    step = 0, // current step in animation
    tempScrollPosition; // Cannot assign any number yet (i.e. 0), because `scrollPosition` may be 0.
  // Get amount of scrolled to bottom
  scrolledToBottom = innerWrapper.scrollHeight - innerWrapper.parentElement.offsetHeight;

  animationID = requestAnimationFrame(smoothScrollAnim); // Start animation

  function smoothScrollAnim() {
    animationID = requestAnimationFrame(smoothScrollAnim); // Restart animation
    // Get scroll position
    var scrollBarPosition = easeOut(step++, start, change, duration);
    scrollBarPosition = clamp(scrollBarPosition, 0, scrolledToBottom)

    scrollBarPosition = scrollBar.offsetHeight * scrollBarPosition / innerWrapper.scrollHeight;

    // Apply scroll movement
    scrollBarThumb.style.top = scrollBarPosition + 'px';

    // Check if scroll finished (either animation finished, or bumped to top or bottom)
    if (step >= duration || tempScrollPosition === scrollBarPosition) {
      // Clean up
      tempScrollPosition = null;
      scrollSpeed = 2;
      cancelAnimationFrame(animationID);
    } else {
      tempScrollPosition = scrollBarPosition;
    }
  }
}


function easeOut(time, begin, change, duration) {
  time /= duration;
  return -change * time * (time - 2) + begin;
}

function clamp(val, min, max) {
  if (typeof min !== 'number') min = 0;
  if (typeof max !== 'number') max = 1;
  return Math.min(Math.max(val, min), max);
}
html {
  height: 100%;
  overflow-y: hidden;
}
body {
  height: 100%;
  overflow-y: hidden;
  display: flex;
}
#outerWrapper {
  height: 400px;
  overflow: auto;
  background-color: black;
}
#content {
  background-image: url("http://images.freeimages.com/images/premium/previews/3037/30376024-beautiful-flower-portrait.jpg");
  width: 400px;
}
#scrollbar {
  height: 400px;
  width: 50px;
  background-color: orange;
  border: 2px solid green;
}
#scrollbar_thumb {
  background-color: yellow;
  border: 2px solid blue;
  position: relative;
}
<div id="outerWrapper">
  <div id="innerWrapper">
    <div id="content">
      Lorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero
      sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus
      Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui scelerisque Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus
      enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar
      justo neque dui ipsum vitae. Lacinia dui scelerisque Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames
      ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui scelerisque
      Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem
      lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor Lorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie
      vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum
      vitae. Lacinia dui scelerisque Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque
      Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui scelerisque Sed convallis nonummy orci
      Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla.
      Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui scelerisque Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet
      consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet
      risus Sed condimentum Cras. Nunc massa mauris tempor dolor pulvinar justo neque dui ipsum vitae. Lacinia dui scelerisque Sed convallis nonummy orci Vestibulum orci tempusLorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat
      Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero sem et laoreet risus Sed condimentum Cras. Nunc massa mauris tempor
      dolor
    </div>
  </div>

</div>
<div id="scrollbar">
  <div id="scrollbar_thumb"></div>
</div>

我认为你把事情复杂化了。
对于滚动,只需注册 "scroll" 事件,
对于捕捉延迟效果,使用 CSS3 transition
对于涉及的简单数学,请参见下面的示例:

var content   = document.querySelector("#content"),
    scrollbar = document.querySelector("#scrollbar"),
    handler   = document.querySelector("#handler");

// FROM ELEMENT SCROLL TO HANDLER POSITION
function moveScrollbar() {

  // Calculate scrollbar height
  var height = content.clientHeight,
      scrollHeight = content.scrollHeight,
      handlerHeight = height * height / scrollHeight;

  // Calculate scrollbar position
  var handlerTop = content.scrollTop / height * handlerHeight;

  handler.style.height = handlerHeight+'px';
  handler.style.top    = handlerTop+'px';

}

moveScrollbar();                                    // At init
content.addEventListener("scroll", moveScrollbar);  // and on scroll
body {
  display: flex;
}

#content{
  height:190px;
  width:300px;
  overflow-y:scroll;
  font-size:3em;
}
#scrollbar{
  position:relative;
  background:orange;
  width:30px;
}
#handler{
  position:absolute;
  top:0;
  background:yellow;
  width: 100%;
  transition: 0.2s;  /* ♪ */
}
<div id="content">
  Scroll and see the custom scrollbar move.<br>Lorem ipsum dolor sit amet consectetuer laoreet faucibus id ut et. Consequat Ut tellus enim ante nulla molestie vitae sem interdum turpis. Fames ridiculus cursus pellentesque Vestibulum justo sem lorem neque accumsan nulla. Lacinia Suspendisse vitae libero
</div>

<div id="scrollbar">
  <div id="handler" draggable="true"></div>
</div>

当你决定构建一个点击拖动来让你的处理程序真正滚动内容时,这里也是数学:

// FROM HANDLER POSITION TO ELEMENT SCROLL:
var height = content.clientHeight,
    scrollHeight = content.scrollHeight,
    scrollPos = scrollHeight / height * handler.clientTop;