最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Scroll to position WITHIN a div (not window) using pure JS - Stack Overflow

programmeradmin3浏览0评论

PURE JS ONLY PLEASE - NO JQUERY

I have a div with overflow scroll, the window (html/body) never overflows itself.

I have a list of anchor links and want to scroll to a position when they're clicked.

Basically just looking for anchor scrolling from within a div, not window.

window.scrollTo etc. don't work as the window never actually overflows.

Simple test case

JADE

nav
  a(data-goto="#1") 1
  a(data-goto="#2") 2
  a(data-goto="#3") 3
  a(data-goto="#4") 4
  a(data-goto="#5") 5
  a(data-goto="#6") 6

main
  p(data-id="1") 1
  p(data-id="2") 2
  p(data-id="3") 3
  p(data-id="4") 4
  p(data-id="5") 5
  p(data-id="6") 6

SCSS

html, body {
  width: 100%;
  height: 100%;
  max-height: 100%;
}

main {
  height: 100%;
  max-height: 100%;
  overflow: scroll;
  width: 500px;
}

nav {
  background: red;
  color: white;
  position: fixed;
  width: 50%;
  left: 50%;
}

a {
  color: white;
  cursor: pointer;
  display: block;
  padding: 10px 20px;
  &:hover {
    background: lighten(red, 20%);
  }
}

p {
  width: 400px;
  height: 400px;
  border: solid 2px green;
  padding: 30px;
}

JS

var links = document.querySelectorAll('a'),
    paras = document.querySelectorAll('p'),
    main  = document.querySelector('main');

for (var i = 0; i < links.length; i++) {
  links[i].addEventListener('click', function(){
    var linkID = this.getAttribute('data-goto').slice(1);
    for (var j = 0; j < links.length; j++) {
      if(linkID === paras[j].getAttribute('data-id')) {
         window.scrollTo(0, paras[j].offsetTop); 
      }
    }
  })
}

PURE JS ONLY PLEASE - NO JQUERY

PURE JS ONLY PLEASE - NO JQUERY

I have a div with overflow scroll, the window (html/body) never overflows itself.

I have a list of anchor links and want to scroll to a position when they're clicked.

Basically just looking for anchor scrolling from within a div, not window.

window.scrollTo etc. don't work as the window never actually overflows.

Simple test case http://codepen.io/mildrenben/pen/RPyzqm

JADE

nav
  a(data-goto="#1") 1
  a(data-goto="#2") 2
  a(data-goto="#3") 3
  a(data-goto="#4") 4
  a(data-goto="#5") 5
  a(data-goto="#6") 6

main
  p(data-id="1") 1
  p(data-id="2") 2
  p(data-id="3") 3
  p(data-id="4") 4
  p(data-id="5") 5
  p(data-id="6") 6

SCSS

html, body {
  width: 100%;
  height: 100%;
  max-height: 100%;
}

main {
  height: 100%;
  max-height: 100%;
  overflow: scroll;
  width: 500px;
}

nav {
  background: red;
  color: white;
  position: fixed;
  width: 50%;
  left: 50%;
}

a {
  color: white;
  cursor: pointer;
  display: block;
  padding: 10px 20px;
  &:hover {
    background: lighten(red, 20%);
  }
}

p {
  width: 400px;
  height: 400px;
  border: solid 2px green;
  padding: 30px;
}

JS

var links = document.querySelectorAll('a'),
    paras = document.querySelectorAll('p'),
    main  = document.querySelector('main');

for (var i = 0; i < links.length; i++) {
  links[i].addEventListener('click', function(){
    var linkID = this.getAttribute('data-goto').slice(1);
    for (var j = 0; j < links.length; j++) {
      if(linkID === paras[j].getAttribute('data-id')) {
         window.scrollTo(0, paras[j].offsetTop); 
      }
    }
  })
}

PURE JS ONLY PLEASE - NO JQUERY

Share Improve this question edited Jul 13, 2015 at 10:55 mildrenben asked Jul 13, 2015 at 10:37 mildrenbenmildrenben 3,7576 gold badges27 silver badges37 bronze badges 4
  • Is there a particular reason you want to use JS rather than just a href? (If so, that's fine, I was just wondering) – icke Commented Jul 13, 2015 at 10:56
  • @icke it is just the way the backend guy has set things up. href wouldn't work anyway as they're in different containers. And href doesn't smooth scroll – mildrenben Commented Jul 13, 2015 at 10:58
  • Different containers, as in, they're loaded in different iframes? Or are they on the same page? I removed all JS from your example, added IDs to the targets and hrefs to the menu items and it scrolled there just fine, though admittedly not smoothly. I feel like I'm missing the point, but I just can't tell... – icke Commented Jul 13, 2015 at 11:20
  • @icke as per the codepen, they're in 2 different containers (divs/other elems), not iframes. And the nav links are populated by the backend, which spits out what I have in the codepen with data- values rather than href. – mildrenben Commented Jul 13, 2015 at 11:38
Add a ment  | 

2 Answers 2

Reset to default 4

What you want is to set the scrollTop property on the <main> element.

var nav = document.querySelector('nav'),
    main  = document.querySelector('main');

  nav.addEventListener('click', function(event){
    var linkID,
      scrollTarget;
    if (event.target.tagName.toUpperCase() === "A") {
      linkID = event.target.dataset.goto.slice(1);
      scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
      main.scrollTop = scrollTarget.offsetTop;
    }
  });

You'll notice a couple of other things I did different:

  • I used event delegation so I only had to attach one event to the nav element which will more efficiently handle clicks on any of the links.
  • Likewise, instead of looping through all the p elements, I selected the one I wanted using an attribute selector

This is not only more efficient and scalable, it also produces shorter, easier to maintain code.

This code will just jump to the element, for an animated scroll, you would need to write a function that incrementally updates scrollTop after small delays using setTimeout.

var nav = document.querySelector('nav'),
    main  = document.querySelector('main'),
    scrollElementTo = (function () {
      var timerId;
      return function (scrollWithin, scrollTo, pixelsPerSecond) {
        scrollWithin.scrollTop = scrollWithin.scrollTop || 0;
        var pixelsPerTick = pixelsPerSecond / 100,
          destY = scrollTo.offsetTop,
          direction = scrollWithin.scrollTop < destY ? 1 : -1,
          doTick = function () {
            var distLeft = Math.abs(scrollWithin.scrollTop - destY),
              moveBy = Math.min(pixelsPerTick, distLeft);
            scrollWithin.scrollTop += moveBy * direction;
            if (distLeft > 0) {
              timerId = setTimeout(doTick, 10);
            }
          };
        clearTimeout(timerId);
        doTick();
      };
    }());

nav.addEventListener('click', function(event) {
  var linkID,
    scrollTarget;
  if (event.target.tagName.toUpperCase() === "A") {
    linkID = event.target.dataset.goto.slice(1);
    scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
    scrollElementTo(main, scrollTarget, 500);
  }
});

Another problem you might have with the event delegation is that if the a elements contain child elements and a child element is clicked on, it will be the target of the event instead of the a tag itself. You can work around that with something like the getParentAnchor function I wrote here.

I hope I understand the problem correctly now: You have markup that you can't change (as it's generated by some means you have no control over) and want to use JS to add functionality to the generated menu items.

My suggestion would be to add id and href attributes to the targets and menu items respectively, like so:

var links = document.querySelectorAll('a'),
    paras = document.querySelectorAll('p');

for (var i = 0; i < links.length; i++) {
    links[i].href=links[i].getAttribute('data-goto');
}

for (var i = 0; i < paras.length; i++) {
    paras[i].id=paras[i].getAttribute('data-id');
}
发布评论

评论列表(0)

  1. 暂无评论