Where does this inaccuracy with DOM scroll events come from? - javascript

Have a look at the code below, there is a jsbin for it too here: http://output.jsbin.com/qosevogeka/
var container = document.querySelector('.container')
var firstEventHandled = false
container.addEventListener('scroll', function(e) {
if (firstEventHandled) return
console.log(e.target.scrollLeft)
firstEventHandled = true
})
.container {
display: flex;
overflow: scroll;
}
.box {
flex-shrink: 0;
width: 40%;
height: 50px;
background: yellow;
}
.box+.box {
margin-left: 15px;
}
<body>
<div class="container">
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
</div>
</body>
If you scroll the resulting view, you will see that 0 is never logged to the scroll which I expect since the scrollLeft was 0 before I started scrolling. Where does this inaccuracy with the scroll events come from? Thanks

Where does this inaccuracy with the scroll events come from? Thanks
As per documentation
The scroll event is fired when the document view or an element has
been scrolled.
So this line
console.log(e.target.scrollLeft)
is logging the e.target.scrollLeft after the scroll has happened, since this function is an event-handler which is executed after the event has happened already. And by definition, once the scroll has happened you cannot be at the starting point.
If you scroll the resulting view, you will see that 0 is never logged
to the scroll which I expect since the scrollLeft was 0 before I
started scrolling
Unfortunately, there is no event called scrollstart like dragstart or mousedown or keydown, so there is no way to know when the scroll had started and we only know once the scroll has happened.

Related

How to change fixed button to change BG color if its scrolled over sections that has same color?

Basically, i have fixed button on bottom that scrolls over the page on mobile. The color of button is yellow and i want when the button scrolls over sections that are same color as button, to get additional class or change style directly inline and set BG color to white.
Is it possible with Observer or something similar?
Thanks!
The trouble with trying to use the Intersection Observer API in this case is twofold:
The yellow sections are not ancestors of the button, they're likely siblings.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element...
The button is position: fixed, which doesn't play nicely with the internals of the API: Intersection observer does not work with target with position: fixed.
The old-school way of doing this would be to check the bounding box of the button against the bounding boxes of the yellow sections each time the page is scrolled.
That means calling Element.getBoundingClientRect() once for the button (it's bounding box should never change because it's position: fixed relative to the viewport) and once for each yellow section each time the scroll event is raised.
Here's a sample showing this approach:
const button = document.getElementById('some-action');
const buttonRect = button.getBoundingClientRect();
const yellowDivs = document.querySelectorAll('div.yellow');
const areIntersecting = (bounds1, bounds2) =>
bounds1.top < bounds2.bottom && bounds1.bottom > bounds2.top;
document.addEventListener('scroll', () => {
/* Use for...of, not .forEach so we can
return early. */
for (let item of yellowDivs) {
const itemRect = item.getBoundingClientRect();
if (areIntersecting(itemRect, buttonRect)) {
button.classList.add('white');
button.classList.remove('yellow');
/* We don't care how many yellow divs the button
is intersecting. Once we've found one, we can
return so we're not computing the rectangles
of the rest. */
return;
}
/* If none of the yellow divs were interecting,
reset the color of the button. */
button.classList.add('yellow');
button.classList.remove('white');
}
});
div.blue, div.white, div.yellow { height: 250px; }
.blue { background-color: blue; }
.white { background-color: white; }
.yellow { background-color: yellow; }
#some-action {
position: fixed;
top: 20px;
right: 20px;
padding: 10px 20px;
}
<div class="blue"></div>
<div class="yellow"></div>
<div class="white"></div>
<div class="blue"></div>
<div class="yellow"></div>
<div class="white"></div>
<div class="blue"></div>
<div class="yellow"></div>
<div class="white"></div>
<div class="blue"></div>
<div class="yellow"></div>
<div class="white"></div>
<div class="blue"></div>
<div class="yellow"></div>
<div class="white"></div>
<button id="some-action" class="yellow">Some Action</button>

Scrolling past certain point does

I'm trying to use a scroll function to run a function once it passes greater than or less than <a> tags. First the starting point which is fixed on the page:
<div style="height: 200px">
<input type="text" id="starting-point" />
<div>
This is setting the starting point at 200px from the top of the page. Then the container behind it could be anything from 1000px to 3000px when scrolling (using the window as scrolling).
<div style="height: 200px;">
<input type="text" id="starting-point" />
<div>
<div style="height: 3000px;">
<!-- ... content here -->
<div style="height: 200px;">
1
</div>
<div style="height: 300px;">
2
</div>
<div style="height: 240px;">
3
</div>
etc...
</div>
What I'm trying to achieve is for each <a> tag that passes the starting point, to do show something. So when scrolling it starts at 1, once 2 reaches the starting point, something on the page (like a textbox) would switch it from 1 to 2, and so on going down, and then work in reverse going back up. This is what I have so far:
$(document).ready(function () {
window.addEventListener('scroll', function (e) {
var setStart = $('#starting-point').offset().top - $(window).scrollTop(); // starting point
var getTag = $('a');
if (setStart >= getTag) {
run function here
}else{
run function here
}
});
});
I don't know how to set a variable to when an <a> tag passes that starting point to pass it into the function to run what I need. There could be 20 <a> tags on the page. Running a for loop I don't think solves the issue.
Here is a demo on how you could do it.
There could be some other way too.
On load, we get the position of the #starting-point and all the anchors which now have the scroll_target class.
Then, on scroll, you have to determine the scrolling direction... Because the logic is slightly different going up compared to going down.
Each time it passes a "target" position, the scroll_target is decremented/incremented.
So you know which anchor just passed because of the position array.
I made a text array to update the input based on the anchor's text that just passed. It could also be the anchor's value or a data-* attribute.
I left all console logs for you to see what is going on.
$(document).ready(function(){
var startPoint = $("#starting-point").offset().top;
console.log(startPoint);
var scrollTargets_pos = [];
var scrollTargets_text = [];
var scrollingDown = true;
var lastScroll = 0;
$(".scroll_target").each(function(){
scrollTargets_pos.push($(this).offset().top);
scrollTargets_text.push($(this).text());
});
console.log(scrollTargets_pos);
console.log(scrollTargets_text);
var passedIndex = -1;
$(window).on("scroll",function(){
var scrolled = $(this).scrollTop();
console.log(scrolled);
// Scroll direction
scrollingDown = (scrolled > lastScroll);
lastScroll = scrolled;
if(scrollingDown){
// Scrolling down...
//console.log("down");
if( scrolled+startPoint > scrollTargets_pos[passedIndex+1] ){
console.log("======================");
$("#starting-point").val(scrollTargets_text[passedIndex+1]);
passedIndex++;
}
}else{
// Scrolling up...
//console.log("up");
if( scrolled+startPoint < scrollTargets_pos[passedIndex] ){
console.log("======================");
$("#starting-point").val(scrollTargets_text[passedIndex])
passedIndex--;
}
}
});
}); // End ready
.startPointDiv{
position: fixed;
top: 100px;
left:0;
width:100%;
border-top: 1px solid red;
text-align: center;
}
.content{
height: 3000px;
margin-top: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="startPointDiv">
<input type="text" id="starting-point" />
</div>
<div class="content">
<!-- ... content here -->
<div style="height: 200px;">
1
</div>
<div style="height: 300px;">
2
</div>
<div style="height: 240px;">
3
</div>
etc...
</div>

How will you detect that a scroll caused by a swipe gesture has ended on a mobile web browser?

I have a horizontal slider for a web app which will be run in mobile browsers. I want to apply some styling right at the moment the scroll ends caused by a swipe gesture. This is the my html and css
.container {
display: flex;
overflow: scroll;
}
.box {
flex-shrink: 0;
width: 25%;
height: 50px;
background: yellow;
}
.box+.box {
margin-left: 15px;
}
<body>
<div class="container">
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
</div>
</body>
Please note that I am not interested in finding when a user has fully scrolled the element, I am specifically interested in when a scroll ends caused by a swipe gesture. There is no scrollStart or scrollEnd event for browsers, there is only scroll event, but scroll event contains no such info allowing me to find that there are no scroll events afterwards.
There are touchstart and touchend events that could help you do what you want to do.
Check this out:https://developer.mozilla.org/en-US/docs/Web/API/Touch_events
The question is quite old, but I couldn't find the answer, and I have just had to solve the problem myself, so I thought I can share this.
On a mobile device, after the swipe is done, and after touchEnd event, scrolling still takes some time. This makes it hard to manipulate scrollLeft and scrollTop properties in touchEnd callback.
I solved this with window.requestAnimationFrame (please note a polyfill is here: https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/)
How it works:
On touchEnd you start recording current scroll of the container - this is currentScroll variable.
You call animation frame, and in the callback compare current scroll of the element (container.scrollLeft) with the recorded currentScroll. If they are the same, that means scrolling has ended. You can log it in the console or do anything else in here.
If they are not the same, update currentScroll to reflect current scroll position and then call animation frame with the same callback, and it will check if container is still scrolling.
Please note that in this case I used only scrollLeft property, since we are only swiping horizontally. The same can be used to test scrollTop.
var container = document.getElementById('container')
var detectScrollEnded = function(ev) {
var currentScroll = container.scrollLeft
function testIfScrolling() {
if (container.scrollLeft !== currentScroll) {
currentScroll = container.scrollLeft
console.log('still scrolling...')
window.requestAnimationFrame(testIfScrolling)
} else {
console.log('scrolling stopped!')
}
}
window.requestAnimationFrame(testIfScrolling)
}
container.addEventListener('touchend', detectScrollEnded)
.container {
display: flex;
overflow: scroll;
}
.box {
flex-shrink: 0;
width: 25%;
height: 50px;
background: yellow;
}
.box+.box {
margin-left: 15px;
}
<body>
<div class="container" id="container">
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
</div>
</body>

Automatically Scrolling a webpage with animating div

I am making a web app. I have created 25 divs.
I have Used jquery fadeIn() by which divs are gradually made and displayed one after another on screen.
But problem is that when 25 divs have been created, scroll is created due to which first 4 divs can be seen but the remaining can't be seen until user scroll the page.
I want that as one by one div is created, the page should automatically scroll to the div recently created and so on this process should be continued until the last div is created.
You can use
$('html,body').scrollTop($(".answer.visible:last").offset().top);
$(function() {
$(".answer").hide();
$('#demo').click(function(e) {
var _div = $('.answer[style*="display: none"]:first');
if (_div.length) {
_div.fadeIn();
$('html,body').animate({
scrollTop: _div.offset().top
},
'slow');
} else {
$(this).text('Done..!');
}
});
});
#demo {
position: fixed;
bottom: 0px;
left: 0px;
}
.answer {
width: 100%;
height: 200px;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="demo">Click here</button>
<div class="answer">1</div>
<div class="answer">2</div>
<div class="answer">3</div>
<div class="answer">4</div>
<div class="answer">5</div>
<div class="answer">6</div>
<div class="answer">7</div>
<div class="answer">8</div>
<div class="answer">9</div>
<div class="answer">10</div>
<div class="answer">11</div>
<div class="answer">12</div>
<div class="answer">13</div>
<div class="answer">14</div>
<div class="answer">15</div>
<div class="answer">16</div>
<div class="answer">17</div>
<div class="answer">18</div>
<div class="answer">19</div>
<div class="answer">20</div>
<div class="answer">21</div>
<div class="answer">22</div>
<div class="answer">23</div>
<div class="answer">24</div>
<div class="answer">25</div>
I think this looks pretty cool when we use slideDown+scrollTop. Check fiddle
Documentations
To get the coordinates
http://api.jquery.com/offset/
Set vertical position of the scroll bar
https://api.jquery.com/scrollTop/
Set horizontal position of the scroll bar
https://api.jquery.com/scrollleft/
I found this link here
smooth auto scroll by using javascript
Using this you could create something like this here:
http://jsfiddle.net/mrc0sp5j/
The main point is, that you create a scrolling-function using
window.scrollBy or window.scrollTo
http://www.w3schools.com/jsref/met_win_scrollto.asp
With jQuery .last or .eq you can specify which element you want to scroll to
$(".mydivobjects").eq(x).position().top
Hope this helps
cheers

How to Slide or Move this Set of Divs

I have a set of seven div's with the following properties:
height: 100px;
width: 100px;
display: inline-block;
I have a wrapper div containing these seven blocks with only enough room to fit four and change.
The overflow is hidden.
How can I make this function so that when you clicked and dragged horizontally, or swiped with your finger on mobile, the entire row of div blocks would slide to show the previously hidden ones?
Please refer to this jsFiddle for the example.
We can use css or jQuery here.
*Bonus, show fractions of otherwise entirely hidden div's at the edges of the container.
Based on jfriend00's answer I modified this so it will work on touch/click and move with the mouse.
var last_x = null;
var holding = false;
//Mark the wrapper as clicked/touched
$('.wrapper').mousedown(function(){
holding=true;
});
//We do this on document so that even if movement goes outside of the container the event will fire
$(document).mouseup(function(){
holding=false;
});
$('.wrapper').mousemove(function(e){
if(last_x === null || !holding) //If this is the first movement
{
last_x = e.pageX;
return;
}
var ammount = e.pageX - last_x;
$('.slider',this).css('margin-left', '+=' + ammount);
last_x = e.pageX;
});
The gist of how this works is that when the mousedown event is detected on the container the script starts tracking all mouse movement and moves the content with the mouse. When the mouse is released it stop tracking movement.
Fiddle: http://jsfiddle.net/NvJam/2/
Since no one has mentioned jQuery.Kinetic I'll add this:
<div class="carousel">
<div class="wrapper">
<div class="first">First</div>
<div class="second">Second</div>
<div class="third">Third</div>
<div class="fourth">Fourth</div>
<div class="fifth">Fifth</div>
<div class="sixth">Sixth</div>
<div class="seventh">Seventh</div>
</div>
</div>
$('.carousel').kinetic();
Demo: http://jsfiddle.net/louisbros/2pRBg/6/
see here
.wrapper {
width: 900px;
height: 100px;
overflow: hidden;
}
You can put an additional container div and use absolute positioning on that div to move the items left/right. Here's a demo:
http://jsfiddle.net/jfriend00/7edc9/
HTML looks like this:
<div class="wrapper">
<div class="slider">
<div class="first">First</div>
<div class="second">Second</div>
<div class="third">Third</div>
<div class="fourth">Fourth</div>
<div class="fifth">Fifth</div>
<div class="sixth">Sixth</div>
<div class="seventh">Seventh</div>
</div>
</div>
You weren't entirely clear how you wanted to move them on non-touch screens, but here's some event handlers that work on buttons:
$("#left").click(function() {
$(".slider").stop(true, true).animate({left: "-=125px"}, 500);
});
$("#right").click(function() {
$(".slider").stop(true, true).animate({left: "+=125px"}, 500);
});
Something similar could be hooked up for touch events.
Even better solution: use the JQuery UI draggable:
$('.slider').draggable({
axis: 'x',
});
http://jsfiddle.net/DCuGV/2/

Categories