I have a div containing a long, multi-screen blog post. The length varies depending on the content. I'd like to trigger a waypoint when a third of the way through the div. I understand the offset function, but that seems to apply to how far down the screen the div appears. I don't have the ability to modify the HTML to include any identifier; I would need to do it through the Javascript entirely.
$('.article-body').waypoint({
handler: function() {
alert('Hit midpoint of my context');
},
context: ".article-body",
offset: $(".article-body").height * 0.33
});
Sample HTML:
<body>
<div class="article-body">
CONTENT CONTENT CONTENT
</div>
<body>
I see Daniel's answer has been accepted, but here's how to do it with Waypoints: An offset function that returns a negative number.
Offsets are always in terms of distance from the top of the element to the top of the window. Let's say we have a 300px tall element. If we set the offset to -100 that would have the effect of triggering when the top third of the element is scrolled past. Now let's make that a dynamic function:
offset: function() {
return this.element.offsetHeight / -3;
}
This is sloppy with global variables, but you'll get the idea; the gist of it is to determine ahead of time where you want your "waypoint" triggered, then watch window scrolling until it reaches that point.
(Note that if your content changes after page load you'll need to recalculate waypointPos. You could calculate it on the fly every time, but the scroll event fires frequently enough that that might cause lagginess; I'd poll the window scroll position on a slower interval rather than do DOM calculations constantly during window scroll.)
// determine the scroll position where we want to do something, which is the element's top offset plus half of its height:
var waypointPos = ($('.hasWaypoint').height() / 2) + $('.hasWaypoint').offset().top;
// watch window scroll until we search that point:
var waypointTriggered = false;
$(window).scroll(function() {
if (!waypointTriggered && $(window).scrollTop() >= waypointPos) {
alert("Halfway there!");
waypointTriggered = true; // don't keep triggering endlessly
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p style="height:200px">This is extra stuff whose height we want to ignore</p>
<div class="hasWaypoint" style="height: 3000px;border:1px solid">This is the big div</div>
Related
I'm trying to implement an HTML infinite scroller in which at any given time there are only a handful of div elements on list (to keep the memory footprint small).
I append a new div element to the list and at the same time I'm removing the first one, so the total count of divs remains the same.
Unfortunately the viewport doesn't stay still but instead it jumps backwards a little bit (the height of the removed div actually).
Is there a way to keep the viewport still while removing divs from the list?
I made a small self contained HTML page (well, it still needs JQuery 3.4.1) which exposes the problem: it starts by adding 5 divs and then it keeps adding a new one and removing the first one every 1 second
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function removeit() {
// remove first one
var tiles = $(".tile");
$(tiles[0]).remove();
}
function addit() {
// append new one
var jqueryTextElem = $('<div class="tile" style="height:100px;background-color:' + getRandomColor() + '"></div>');
$("#inner-wrap").append(jqueryTextElem);
}
function loop() {
removeit();
addit();
window.setTimeout(loop, 1000);
}
addit();
addit();
addit();
addit();
addit();
loop();
<div id="inner-wrap"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
You can temporarily add position: fixed to the parent element:
first add position: fixed to the parent;
then remove the item;
then remove position: fixed from the parent
I have a feeling you're trying to have your cake and eat it, in that if you get the viewport to be "still", I think you're meaning you don't want a user to see the scrollbar move and then also not have any new affordance to scroll further down the page, because you would want the scrollbar thumb/grabber to still sit at the bottom of the scrollbar track?
I mean, you could just use $(window).scrollTop($(window).scrollTop() + 100); in your example to make it so the scroll position of the viewport won't visually move when removing elements, but at that point, you wouldn't be keeping the users view of the current elements the same or even allowing a user to have new content further down the page to scroll towards. You'd just be "pushing up" content through the view of the user?
If you are trying to lighten the load of what is currently parsed into document because you are doing some heavy lifting on the document object at runtime, maybe you still want to remove earlier elements, but retain their geometry with some empty sentinel element that always has the height of all previously removed elements added to it? This would allow you to both have a somewhat smaller footprint (though not layout-wise), while still having a usable scrollbar that can communicate to a user and both allow a user to scroll down, towards the content that has been added in.
All in all, I think what you currently have is how most infinite scrollers do and should work, meaning the scroll position and scrollbar should change when content is added in the direction the user is scrolling towards, this communicates to them that they can in fact keep scrolling that way. You really shouldn't want the viewports scroll position to be "still".
To see more clearly why I don't think you have an actual issue, replace your loop() definition with something like this...
function loop() {
$(window).scroll(function() {
// check for reaching bottom of scroller
if ($(window).scrollTop() == ($(document).height() - $(window).height())) {
addit();
removeit();
}
})
}
I have a parallax web page with a series of modules that contain a large photo and a smaller text box that overlays the photo and sits absolutely positioned at the bottom of it. While the size of the photos change, the text box is consistently 340px. Initially, when the site scrolls, the text box is hidden (I am doing a translateY(340px) and hiding the overflow on the container).
I know how to determine when to start revealing the box:
window.addEventListener('scroll', self.monitorScroll, false);
var moduleOffset = Math.floor($el.offset().top);
var moduleTriggerPos = moduleOffset - self.windowHalfHeight; //trigger animation when module is halfway up the screen
self.monitorScroll = function(){
self.yPos = window.pageYOffset;
if (self.yPos > thisObject.triggerPos){
//BEGIN ANIMATION
}
}
but I don't know how to tell the object how much to move each time the listener is triggered. The number of times it is called seems to be based on how fast you scroll, and the amount I would need to move the object differs, as the container resizes with the browser (the narrower the browser, the smaller the photo becomes).
I am currently using a static amount to move the object:
if (newPos >= 0){ //if our object hasn't reached a translateY(0px) value
thisObject.curPos = 320 //starts at 320, the amount it has been translated by in order to hide it
var newPos = thisObject.curPos - 25; //the amount we move it each time
thisObject.textEl.css({ translate: [0,newPos] }); //move it by 25px
thisObject.curPos = newPos; //update the current position
}
How do I get more accurate determination of how much to move the item by, rather than using a static movement amount. I want it to be based on the percentage of the way I've scrolled toward the module's final reveal position, which would ideally be when the module was fully at the top of the screen at full browser width (max width of 1200px), or some percentage thereof if they had resized the browser smaller.
I don't need exact code, but more just a conceptual understanding of what I should be monitoring / calculating to determine the correct positioning. Thanks!
I figured this out. For various reasons I made the trigger point for the CSS animation start when it first appears on screen rather than halfway up, then used this code:
var scrollProgress = (((self.windowHeight+self.yPos)-thisObject.offset)/thisObject.height);
var scrollAmt = scrollProgress*self.moduleTextHeight;
var translateAmt = -scrollAmt;
if (scrollAmt <= self.moduleTextHeight){
thisObject.textEl.css({ translate: [0,translateAmt] });
} else {
thisObject.textEl.css({ translate: [0,-self.moduleTextHeight] });
}
My problem
I am making a vertical website for a client who wishes to have the window "snap" to the nearest page when most of the element is visible in the viewport. So, if the page is 85% visible, it should scroll to be 100% visible.
My problem is that occasionally when scrolling all the way to the top or bottom of the viewport, the viewport will "stick" to the first or last element, preventing a few scroll events and causing a highly noticeable flicker.
A working fiddle is here: http://jsfiddle.net/RTzu8/1/
To reproduce the error, use the scrollbar to scroll to the bottom of the page. Then, scroll up with your mousewheel. You should see the flicker. Sometimes it takes a few refreshes or attempts, but the issue is highly reproducible.
I'm at a loss as to what could be causing this issue. See below for a run-down of my code and what I have tried to prevent it so far.
My code
To accomplish my snapping, I needed to detect whether an element was a certain percentage visible. So, I added a jQuery function, isNearScreen, below. I have thoroughly tested this function, and as far as I can tell it returns accurate results.
//Modification of http://upshots.org/javascript/jquery-test-if-element-is-in-viewport-visible-on-screen
//Returns "true" if element is percent visible within the viewport
$.fn.isNearScreen = function(percent){
var offset = 1 - percent;
var win = $(window);
var viewport = {
top : win.scrollTop()
};
viewport.bottom = viewport.top + win.height();
var bounds = this.offset();
bounds.bottom = bounds.top + this.outerHeight();
bounds.top = bounds.top;
//If the element is visible
if(!(viewport.bottom < bounds.top || viewport.top > bounds.bottom)){
//Get the percentage of the element that's visible
var percentage = (viewport.bottom - bounds.top) / this.height();
//If it's greater than percent, but less than 1 + (1 - percent), return true;
return (percentage > (1 - offset) && percentage < (1 + offset));
}
return false;
};
I then created a snap function, which makes use of Underscore.js's _.debounce function, to only fire on the trailing end of continuous scroll events. It fires after a 500ms timeout, and I am fairly (though not 100%) convinced that it is firing correctly. I have not been able to reproduce console logs that would indicate multiple concurrent firings.
//Snaps the screen to a page after scroll input has stopped arriving.
var snap = _.debounce(function(event){
//Check each page view
$.each($('.page-contents'), function(index, element){
//If the page view is 70% of the screen and we are allowed to snap, snap into view
if($(element).isNearScreen(0.7)){
$('html,body').animate({
scrollTop: $(element).offset().top
}, 300);
}
});
}, 500);
Finally, I bind to the window's scroll event
$(window).on('scroll', snap});
The (extremely simplified) HTML:
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
<div class="page">
<div class="page-contents"></div>
</div>
and CSS:
.page{
height: 750px;
width: 100%;
margin: 10px 0;
background: gray;
}
.page-contents{
height: 100%;
width: 100%;
}
What I've tried
I have tried the following, with no success:
Setting a boolean, 'preventSnap', on the window, checking its state, and only firing the animate portion of snap if it is set to false. After animation, set it to true, then set it to false after 500ms (which should in theory prevent double firings).
Calling .stop() on the element before running the snap animation.
Calling event.preventDefault() on the scroll event before running the animation.
Reducing and increasing my _.debounce delay. Interestingly, a lower _.debounce delay (200-300ms) seems to aggravate the problem and a higher _.debounce delay (1000ms) seems to fix it. This is not an acceptable solution, however, as it feels "long" waiting 1sec for the page to "snap".
Changing the heights of the elements
If there is any other information I can provide, please let me know. I'm at a loss!
I think this is a combination of events and how _.debounce works. I noticed in the fiddle (in Chrome) that the elements were 'jitterring' long after the snap finished. If you put a console log in the snap event handler you can see it's constantly being called after a snap even with no scroll inputs.
This must be the scroll animation itself setting off the snap, I tried to set a flag to prevent dual snapping and clearing the flag after the animation was finished -- however that didn't work I think because _.debounce is queuing the event to happen later (after the animation finishes and clears the flag).
So what does work is to add this as the start of the snap handler:
var nosnap = false;
var snap = _.debounce(function(event){
// Don't snap if already animating ...
if (nosnap) { nosnap = false; return; }
nosnap = true;
Fiddle
That prevents the animation directly firing the next snap event -- however that's going to cause issues if you scroll again during the animation.
So, that's a bit of a hack. Ideally you want to be able to tell what's causing the scroll event and react accordingly but there's no easy way to do that.
I absolutely think you need to stop the animation when handling a second scroll event as well.
okay heres the scenario. I have a one page website with may sections using anchor links. Whe the user is on a secondary layout (page) and when they click on to go to a section on the main page again, for some reason the graphics dont load properly until a scroll happens. All I want to do is whenever the main layout is loaded, no matter which anchor it loads to, simply scroll the page up or down by 1 pixel.
$.scrollTo({ top: '+=100px', left: '+=0px' }, 800);
I tried the above, but this code simply takes the user 100 pixels from the top. I don't want that to happen, i.e. not from the top but from where ever the user is on screen.
use jquery scrollTop() to set the scroll position to the current scroll position + 1:
$(window).scrollTop($(window).scrollTop()+1);
I have a similar problem. I want to scroll down 1 pixel and then 1 pixel up, so the user hopefully won't notice anything at all. I did this:
window.scrollBy(0, 1); // 0 pixels horizontal and 1 pixel down
window.scrollBy(0, -1); // 0 pixels horizontal and 1 pixel up
UPDATE:
But I might end up using JQuery instead. All I want is the scroll event to fire and I can do that with:
$(window).scroll();
A pure JavaScript solution without the jQuery overhead:
window.scrollY += 100;
With jQuery (and a fancy animation):
var cur = $(window).scrollTop();
$(window).animate({scrollTop: cur + 100});
$("html, body").animate({scrollTop: ($(window).scrollTop() + 1)});
Basically I have a list of divs like this:
<div id="1">First div</div>
<div id="2">Second div</div>
and I want the most visible div to affect what is displayed in a different constant div, like this:
<div id="link">First div's link</div> (If the first div took up most of the page)
And then
<div id="link">Second div's link</div> (If the second div is scrolled to)
How would I get the Javascript to figure out which div is being viewed (calculated by which is taking up the greatest % of pixel space on the screen) and then trigger an event for the 'link' div based on that?
EDITING MY PREVIOUS ANSWER.
You have to find out how far each div is from the top of the document (it's a fixed number, regardless of how far the window is scrolled down):
divTop = $('#1').offset().top
Then, find out how far the window is scrolled:
scrollTop = $(document).scrollTop()
You also need the window's height (which is the height of visible portion of the document):
windowHeight = $(window).height()
And, you need the height of each div:
divHeight = $('#1').height().
Then you calculate where each div starts relative to the top of the window, which could be a negative number -- if the window is scrolled down past the top of the div -- in which case you make it zero, using Math.max:
divTopInWindow = Math.max(0, divTop - scrollTop)
Similarly, you calculate the div's bottom, relative to the top of the window. If the div extends past the bottom of the window, you make it the window's height:
divBottomInWindow = Math.min(windowHeight, divTop + divHeight - scrollTop)
Finally, you figure out the percent visible of each div like so (actually it's the fraction, not the percent, but whatever):
percentVisible = (divBottomInWindow - divTopInWindow) / windowHeight
I haven't tried it, so there may be a mistake in there, but it's definitely the right approach.
-------------------------- OLD ANSWER BELOW JUST FOR REFERENCE ------------------
I don't think you're using the expression "trigger an event" properly. Sounds like you just want to set the content of a div based on some condition (i.e. whether div 1 is taller than div 2).
Assuming you have jQuery or Zepto loaded on the page:
if( $('#1').height() > $('#2').height() ) {
$('#link').html('First div link');
}
else {
$('#link').html('Second div link');
}