Set Scrolltop and Scrollleft Without JavaScript

Set scrollTop and scrollLeft without JavaScript

I'm afraid that this is not possible using CSS/HTML only.

Edit [05.01.2012] - A possible solution

I created a solution that works in the following browsers:

  • Firefox 4+
  • Safari 5+
  • Chrome 6+
  • Opera 11+
  • IE 10+
  • Android 2.3+

It's really a bit hacky, so see whether you would use it or not. :)

A little explanation

I used the HTML5 attribute autofocus on an <input>-field. As this will focus the input, it has to get it into the viewport. Therefor it will scroll to the given position. To get rid of the highlighted outline and to not see the input at all, you have to set some styles. But this still forced Safari to have one blinking pixel, so I did the trick with the span, that acts like an overlay. Note that you can't simply use display: none as this won't trigger the autofocus (only tested this in Safari).

Demo

The demo will run in Safari and Chrome only. IE and Firefox seem to not fire autofocus in an <iframe>.

div.outer {  height: 100px;  width: 100px;  overflow: auto;}
div.inner { position: relative; height: 500px; width: 500px;}
div.inner>input { width: 1px; height: 1px; position: absolute; z-index: 0; top: 300px; left: 200px; border: 0; outline: 0;}
div.inner>span { width: 1px; height: 1px; position: absolute; z-index: 1; top: 300px; left: 200px; background: white;}
<div class="outer">  <div class="inner">    <input type="text" autofocus></input>    <span></span>  </div></div>

Make scrollLeft / scrollTop changes not trigger scroll event listener

Easiest generic method? Just reset the flag in the event handler. You'll want to check first if you're actually changing the value, as otherwise an event won't be fired - as Rodrigo notes, a good practice here is to factor this out into so-called "setter" functions:

function setScrollLeft(x)
{
if ( element.scrollLeft != x )
{
ignoreScrollEvents = true;
element.scrollLeft = x;
}
}

...

function onScroll()
{
var ignore = ignoreScrollEvents;
ignoreScrollEvents = false;

if (ignore) return false;

...
}

But, depending on your needs, you may already be storing the scroll position somewhere; if so, just update and check that as your flag. Something like:

element.scrollLeft = currentScrollLeft = x;

...

function onScroll()
{
// retrieve element, etc...

if (element.scrollLeft == currentScrollLeft) return false;

...
}

scrollTop or scrollLeft on horizontal website?

For horizontal scrolling I would go the following route, simplifying your HTML and whaty you listen for. Since touch devices can easily just swipe to scroll, all you need to do is make it accessible for people with scroll wheels. You could also add an animation, but it makes this snippet too long.

const main = document.querySelector( 'main' );const nav = document.querySelector( 'nav' );
let scrollend;
function onwheel(){ /* When using the scrollwheel, translate Y direction scrolls to X direction. This way scrollwheel users get the benefit of scrolling down to go right, while touch and other users get default behaviour. */ event.preventDefault(); event.stopImmediatePropagation(); main.scrollLeft += event.wheelDeltaY; }function onscroll(){ /* When scrolling, find the nearest element to the center of the screen. Then find the link in the nav that links to it and activate it while deactivating all others. */ const current = Array.from( main.children ).find(child => { return child.offsetLeft >= main.scrollLeft - innerWidth / 2; }); const link = Array.from( nav.children ).reduce((find, child) => { child.classList.remove( 'selected' ); return find || (child.href.indexOf( current.id ) >= 0 ? child : find); }, false); if( link ) link.classList.add( 'selected' ); clearTimeout( scrollend ); scrollend = setTimeout( onscrollend, 100 ); }function onscrollend(){ /* After scrolling ends, snap the appropriate element. This could be done with an animation. */ clearTimeout( scrollend ); const current = Array.from( main.children ).find(child => { return child.offsetLeft >= main.scrollLeft - innerWidth / 2; }); main.scrollLeft = current.offsetLeft; }
/* Bind and initial call */
main.addEventListener( 'wheel', onwheel );main.addEventListener( 'scroll', onscroll );
onscroll();
html,body,main {    height: 100%;}body {    padding: 0;    margin: 0;}main {    display: flex;    overflow: auto;    width: 100%;    height: 100%;    scroll-snap-type: x proximity;}main section {    width: 100%;    height: 100%;    flex: 0 0 100%;}nav {    position: fixed;    bottom: 0;    left: 0;    width: 100%;    display: flex;    justify-content: center;    align-items: center;}nav a {    width: 1em;    height: 1em;    margin: 1em;    display: block;    overflow: hidden;    color: transparent;    border: 1px solid black;    border-radius: 50%;}nav a.selected {    background: black;}
.bland { background: gray; }.dark { background: darkgray; color: white; }.bright { background: yellow; }
<nav>    <a href="#section-1">Section 1</a>  <a href="#section-2">Section 2</a>  <a href="#section-3">Section 3</a>  </nav>
<main> <section class="bright" id="section-1"> <h2>Section 1</h2> </section> <section class="dark" id="section-2"> <h2>Section 2</h2> </section> <section class="bland" id="section-3"> <h2>Section 3</h2> </section> </main>

Is scrollTop, and scrollLeft for overflow hidden elements reliable?

Yes, even if an element has CSS overflow set to hidden,

Javascript Element.scrollTop(), Element.scrollLeft() allows you to manipulate the element's scroll position if the element contains overflowing children.

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop

Here's a quick use case:

Animate gallery using scrollLeft

var GAL = $("#gal"),    n = GAL.find(">*").length;    c = 0;
$("button").on("click", function(){ GAL.animate({ scrollLeft: (++c%n) * GAL.width() });});
#gal {    height:     40vh;  overflow:   hidden; /* !! */  white-space:nowrap;  font-size:  0;  } #gal>* {    font-size:      1rem;  display:        inline-block;  vertical-align: top;  width:          100%;  height:         inherit;  background:     50% / cover;  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="gal"> <div style="background-image:url(//placehold.it/800x600/cf5);"></div> <div style="background-image:url(//placehold.it/800x600/f0f);"></div> <div style="background-image:url(//placehold.it/800x600/444);"></div></div>
<button>scrollLeft</button>

scrollTop & scrollLeft do not work on display:none elements

Use visibility: hidden; or some class like this instead:

.hide {
position: absolute !important;
top: -9999px !important;
left: -9999px !important;
}

Or this (from Boilerplate):

.visuallyhidden { 
position: absolute;
overflow: hidden;
clip: rect(0 0 0 0);
height: 1px; width: 1px;
margin: -1px; padding: 0; border: 0;
}

Something with display: none; has no location on the page, as you probably knew.

Check out this article on the subject.

Why sometime scrollTop/scrollLeft not writable?

You can't scroll down below the bottom (or past the right) of the element's contents. If the element has overflow: visible (the default), or is at least as large as its contents, then both scroll properties will be stuck at 0. Similarly, even if there is something hidden to scroll into view, then you won't be able to scroll past the end; if you set the scrollTop or scrollLeft to a larger value than makes sense, it will decrease to the largest value that makes sense.

My guess is in your case you're trying to scroll the div before its content is loaded, and it's refusing to scroll because there isn't anything to scroll into view.

Is there a way to detect horizontal scroll only without triggering a browser reflow

I think your code is right, because at the end of the day you need to read one of those properties to find out the scroll direction, but the key thing here to avoid performance problems is to throttle the event, because otherwise the scroll event fires too often and that is the root cause for performance problems.

So your example code adapted to throttle the scroll event would be like this:

var ticking = false;
var lastScrollLeft = 0;
$(window).scroll(function() {
if (!ticking) {
window.requestAnimationFrame(function() {

var documentScrollLeft = $(document).scrollLeft();
if (lastScrollLeft != documentScrollLeft) {
console.log('scroll x');
lastScrollLeft = documentScrollLeft;
}

ticking = false;
});
ticking = true;
}
});

Source: https://developer.mozilla.org/en-US/docs/Web/Events/scroll#Example

ScrollLeft ScrollTop, How do they work?

The getElementById function is not global on its own -- it is under the global document object. So you must use var header = document.getElementById('header');


Next, the browser's native scrollTop and scrollLeft functions used on a DOM element will tell you the scroll inside that element. For instance, for scrollTop is the measurement from the top of the element (which may be hidden because of scroll) to the top visible edge (In other words, it is the distance above the visible part of the element -- the part that's been hidden because of internal scrolling). If the top of the header element is always completely visible (and the header doesn't have it's own scrollbar), then this distance will always be zero.

To position the header relative to the scroll of the browser window, you may want to get the pageYOffset and pageXOffset on the window element instead, and then position the header based on that. For instance, window.pageYOffset + 15.

Here is a safer alternative that checks various possible places for the page's scroll (helps with browser compatibility).

var scrollTop = function(){
return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
}

var scrollLeft = function(){
return (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
}

The last thing is that element.style.top should be given a string value that is like "10px", so you will need to do the calculation first and then add + 'px' at the end.

eg. header.style.top = window.scrollTop + 15 + "px";


Putting that all together:

var scrollTop = function(){
return (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
}

var scrollLeft = function(){
return (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
}

function test() {
var header = document.getElementById('header');
header.style.top = scrollTop() + 15 + 'px';
header.style.left = scrollLeft() + 15 + 'px';
}

Good luck! Hope that helps!



Related Topics



Leave a reply



Submit