I'm using Isotope (with Packery) to create some fancy responsive grid on my website. With jQuery I'm trying to get all elements in the first row and give them some styles:
$('.grid .grid-item').filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
On large screens there are two grid-elements in a row. On small screens there is only one. To make my script responsive I've added $windows.on('resize') around my script.
$(window).on('resize', function(){
$('.grid .grid-item').filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
});
The script works quite fine, but if I resize the screen to get the small-version (grid-items with 100% width) the second grid-item still has the css-property. Is there a way to get rid of the css-style on resize?
Check it out (and resize the screen)!
The problem with the method you have implemented is that Isotope takes time to reposition each .grid-item, so at the time you are checking for items with a top of 0 they may not have finished moving into their final position.
In addition, you are not resetting the items' opacity so once an item has been given the style it doesn't change back.
And finally, the resize event is called many times during the resize and should be debounced to only actually execute the changes once the resize event has stopped. Something like:
var debouncer;
$(window).on('resize', function(){
clearTimeout(debouncer);
debouncer=setTimeout(function(){
$('.grid .grid-item').css("opacity", "1").filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
},200);
});
https://codepen.io/anon/pen/oYMKRj
So even with the above amends you will struggle to determine which .grid-items are at the top unless you wait for Isotope to complete its animation (which is a clue to how we solve this)...
So How?
Luckily Isotope and Packery have their own custom events you can listen for. So instead of the above you can just listen for the layoutComplete event which is triggered (as you might expect) when the layout is completed...
$grid.on('layoutComplete', function(event,items){// when the layout completes
$('.grid .grid-item')// get all grid-items
.css("opacity", "1")// reset their opacity
.filter(function() {// filter to return
return $(this).css('top') == '0px'; // the items with a top of 0
})
.css("opacity", "0.5");//set the opacity of the matched items
});
https://codepen.io/anon/pen/vyzBYy
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 static images that I want to animate in when the user scrolls to the bottom of where they would be (the bottom of their rectangle). I've tried using the code:
$(window).on("scroll", function(){
if($("body").scrollTop() === 500){
$(window).off("scroll");
//Do stuff here
$('.context_right').fadein(4000);
}
});
With .context_right being the that contains the image I want to slide in from off the right side of the screen. I have tested this in Chrome debugger but there is not animation at all and I can't see why it's not being fired when scrolling down.
You're checking if the scroll position of the window is exactly equal to 500px. This is likely incorrect. You probably only want to check and see if the user has scrolled past 500px.
Consider changing:
if($("body").scrollTop() === 500)
to:
if($("body").scrollTop() >= 500)
Also, be careful with this:
$(window).off("scroll");
If you use any libraries that rely on the scroll event on the window, you would be unbinding all of those events in addition to your own.
Consider refactoring to something like the following:
var scrollAnimateIn = function(){
if($("body").scrollTop() >= 500){
$(window).off("scroll", scrollAnimateIn);
//Do stuff here
$('.context_right').fadein(4000);
}
};
$(window).on("scroll", scrollAnimateIn);
var AddFootnoteScrollIndicator = function(){
$('.mobileFootnote').on('scroll touchmove', function (event) {
var scrollTop = that.$mobileFootnote.scrollTop();
if (scrollTop <= 20){
var opacity = 1 - (scrollTop/20);
$('.scroll-down-indicator').css({'opacity': opacity });
}
});
};
As the user scrolls down, the indicator slowly fades out until it is gone. They scroll back up, the indicator slowly re-appears. They stop in the middle, the indicator is half-visible.
Code works fine, but modifying the opacity via .css() seems expensive. Is there a more clever way of doing this via css or...
I don't want to delay the .on() polling because the animation needs to respond quickly to the scroll.
Any ideas?
When it comes to scroll events, modifying the css via javascript is the only way to go. There is not a way with pure CSS to detect scroll positions like you can with media queries and screen sizes.
The jquery css() function is setting the element.style.opacity property under the hood. You are only one short abstraction layer from the actual element property, so it is not "expensive".
The most costly part of that call would be the $('.scroll-down-indicator') selector, as it has to perform a DOM traversal to find elements with the class name.
Imagine you have a single item carousel that sits at the absolute top of the page. Under that, you have the rest of your content. As the carousel is nested arbitrarily deep in the page, you've set it up to be absolutely positioned. But now you need to know how high the carousel is so you can put a top margin on the rest of the content so it does not overlap. This is what I am doing.
$('#carousel').owlCarousel({
items: 1,
onInitialized: adjustStretchHeader,
onResized: adjustStretchHeader
});
function adjustStretchHeader () {
setTimeout(function () {
return $('.page > .container')
.css('margin-top', $('.page > .stretch-header')
.height() + 15);
}, 250);
}
On the initialisation of the carousel and on resize events, I am getting it to get the carousel's height and update the top margin. The problem is that, without having a delay, the initialized event is triggering before the carousel is fully drawn on the page, so the height is unreliable.
With a delay, I'm able to get it properly. But this is obviously a hack, and I cannot guarantee that on slower devices, the carousel will have been drawn in time.
I cannot see any other useful events in the documentation.
Demonstration fiddle
You can use jQuery's get() method to grab the height of the images (even when they're hidden). This is independent of owlCarousel so don't have to wait for it to finish loading!
var images = $('#carousel img');
var firstImgHeight = images.get(0).height;
$('.page > .container').css('margin-top', firstImgHeight + 15);
Looking through the docs and the demos, I found this page: http://www.owlcarousel.owlgraphic.com/demos/events.html
It shows that the onRefreshed event is called after onInitialized and after the images have loaded. You can use that.
$('#carousel').owlCarousel({
items: 1,
onRefreshed: adjustStretchHeader
});
function adjustStretchHeader () {
return $('.page > .container')
.css('margin-top', $('.page > .stretch-header')
.height() + 15);
}
Fiddle
Although, I agree that the Docs aren't great at explaining the flow of the events. This still feels like a hack to me.
After initialization of carousel, DOMs of images have width but they don't have height until images are actually loaded. Try to use (width)*(aspect ratio) instead of height.
function adjustStretchHeader() {
return $('.page > .container')
.css('margin-top', $('.page > .stretch-header')
.width() * aspectratio + 15);
}
I am creating a site in which there are a number of fixed background images that you scroll past. Associated with each fixed background is an image slider (or text) that is hidden until the title is clicked on. These items are all fixed positioned.
I was able to make this work by using z-index to place items in order top to bottom/first to last and then have each disappear in turn using:
$(document).scroll(function() {
$('#porttitle').toggle($(this).scrollTop() < 225);
});
However, I am unable to use this because the length pixel distance down on the page changes based on the screen size. I am pretty new to Jquery but wanted to try to use .offset .top to have the item disappear not based on the pixel length to the top of the page but instead when an element appears on the screen. This is what I have so far but it isn't seeming to work.
$(document).scroll(function() {
$('#porttitle').toggle($(this).scrollTop() < $(‘article.post-100’).offset().top);
});
Here is the link to the site: http://s416809079.onlinehome.us (not final location - just developing)
Any thoughts?
Thanks!
I think this may work for you, read the comments on the code for a line by line explanation.
Working Example
$(window).scroll(function () { // When the user scrolls
$('div').each(function () { // check each div
if ($(window).scrollTop() < $(this).offset().top) { // if the window has been scrolled beyond the top of the div
$(this).css('opacity', '1'); //change the opacity to 1
} else { // if not
$(this).css('opacity', '0'); // change the opacity to 0
}
});
});
I'm conditionally changing the opacity rather than using toggle because:
...jQuery does not support getting the offset coordinates of hidden
elements or accounting for borders, margins, or padding set on the
body element.
While it is possible to get the coordinates of elements with
visibility:hidden set, display:none is excluded from the rendering
tree and thus has a position that is undefined.
Related documentation:
.offset()
.each()
.scroll()
.scrollTop()