how to highlight bottom li when user scroll div? - javascript

Could you please tell me how how to highlight bottom li when user scrolling in a div? I have one container div, in which there are four divs. In the footer I also have four li (first, second, third,fourth). I want to select the li (background become red)when the user scrolls the respectively div's.
Example
When the code runs, the first li should be selected it background become red because the first div is in the view port. If the user scrolls and moves to the second div, the second li should be selected. And so on.
I tried like that
https://jsbin.com/giwizufotu/edit?html,css,js,output
(function(){
'use strict';
$(function(){
$( "#container" ).scroll(function() {
console.log('scrlling');
if (elementInViewport2($('#first'))) {
// The element is visible, do something
console.log('first visible')
} else {
console.log('second visible')
}
});
})
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
})()
I don't want to use plugin

https://jsbin.com/borohoheji/edit?html,css,js,console,output
Look at what i did i check if the element is visible with .is(':visible')
You can work from there and do exactly what you want

Change your code to:
(function(){
'use strict';
$(function(){
$( "#container" ).scroll(function() {
console.log('scrlling');
if (elementInViewport($('#first'))) {
// The element is visible, do something
console.log('first visible')
} else {
console.log('second visible')
}
});
$( "#container >div" ).hover(
function() {
$(this).css('color', 'yellow');
});
})

First, do the following :
give all the text divs a classname eg 'para', to make them more readily selectable as a collection.
establish a ul.fC li.active {...} directive in your style sheet to give the desired visual effect.
Then :
(function() {
'use strict';
$(function() {
var $container = $("#container"),
$paras = $container.children(".para"), // the four text divs.
$listElements = $(".footer ul.fC li"), // the four li elements in the footer.
oldIndex = -1;
$container.scroll(function() {
var index = $paras.index($paras.filter(visibleY).eq(0)); // position of the first visible text div.
if(index !== oldIndex) { // avoid unnecessary work
$listElements.eq(oldIndex).removeClass('active'); // remove highlight
$listElements.eq(index).addClass('active'); // add highlight
oldIndex = index; // remember index for next event turn
}
}).trigger('scroll');
function visibleY() {
// based on http://stackoverflow.com/a/21627295/3478010
var el = this; // because function is called as a .filter() callback.
var rect = el.getBoundingClientRect(),
top = rect.top,
height = rect.height,
el = el.parentNode;
do {
rect = el.getBoundingClientRect();
if (top <= rect.bottom === false) return false;
// Check if the element is out of view due to a container scrolling
if ((top + height) <= rect.top) return false
el = el.parentNode;
} while (el != document.body);
// Check its within the document viewport
return top <= document.documentElement.clientHeight;
};
});
})();
As written above, the change of style will happen in response to paras exiting/entering the container's top edge.
The behaviour can be changed to respond to paras exiting/entering the container's bottom edge by replacing :
var index = $paras.index($paras.filter(visibleY).eq(0)); // position of the first visible para.
with :
var index = $paras.index($paras.filter(visibleY).last()); // position of the last visible para.
Choose whichever is more desirable.

Related

getBoundingClientRect() showing different values on load vs scroll

I'm working on a site for a client and trying to implement custom parallax functionality. I have used the following code -
var inView = function(element) {
// get window height
var windowHeight = window.innerHeight;
// Get Element Height
var elementHeight = element.clientHeight;
// get number of pixels that the document is scrolled
var scrollY = window.scrollY || window.pageYOffset;
// get current scroll position (distance from the top of the page to the bottom of the current viewport)
var scrollPosition = scrollY + windowHeight;
var elementPosition = element.getBoundingClientRect().top + scrollY;
var elementScrolled = elementPosition + element.clientHeight + windowHeight
// is scroll position greater than element position? (is element in view?)
if (scrollPosition > elementPosition && scrollPosition < elementScrolled) {
return true;
}
return false;
}
// Get all the elements to be parallaxed
const parallaxElements = {
element: document.querySelectorAll('#header-image img'),
ratio: 0.25
}
// The parallax function
const parallax = elements => {
let items = [...elements.element],
itemRatio = elements.ratio
if ('undefined' !== items && items.length > 0 ) {
items.forEach( item => {
if ( inView(item) == true ) {
item.style.transform = 'translate3d(0, ' + (itemRatio * (window.innerHeight - item.getBoundingClientRect().top)) + 'px ,0)'
}
})
}
}
//If element is in viewport, set its position
parallax(parallaxElements)
//Call the function on scroll
window.onscroll = () => {
parallax(parallaxElements)
}
It's working ok except that when the page is loaded initially and the user starts scrolling, the position of element (#header-image img in this case) changes abruptly. I did some digging and noticed that the value of getBoundingClientRect().top is causing the issue.
When the page is loaded, it has some value, and as soon as the user starts scrolling, it abruptly changes to another value.
I am not able to figure out why this is happening. getBoundingClientRect().top is supposed to get the value of element from top of viewport, right?
Any help is greatly appreciated. Thanks!
Pls check the screenshot of inspect element here -
https://i.stack.imgur.com/RYDvK.jpg

Changing logo color on scroll if background has the same color

my project (within Wordpress/elementor) has a number of .slide sections which have 100vh and 100vw.
it also has a fixed header with a svg #logoken hovering on the top of the page.
on scrolling down, the svg logo with id #logoken has to change color as determined in the .slide data attribute data-logo
i wrote the following code and the color change happens, but only for a second and in scrolling up, it doesn't work. i believe it has to do with my check for the slide positione, but i don't see how.
jQuery(document).ready(function( $ ) {
//Scroll action for logo & menu colors
$( window ).scroll( function(e) {
$('.slide').each( function(){
var theSlide = this;
var top_of_element = $(theSlide).offset().top;
var bottom_of_element = $(theSlide).offset().top + $(theSlide).outerHeight();
var bottom_of_screen = $(window).scrollTop() + $(window).innerHeight();
var top_of_screen = $(window).scrollTop();
if( (top_of_element >= top_of_screen) && (top_of_element < (top_of_screen + $(theSlide).height())) ) {
if ( $(theSlide).data('logo') == 'black'){
if(!$("#logoken").hasClass("black")){
$("#logoken").addClass("black");
}
}
else{
if($("#logoken").hasClass("black")){
$("#logoken").removeClass("black");
}
}
console.log ($(this).attr('id') +" is on top: and logocolor is" + $(theSlide).data('logo'));
}
});
});
});
this is temporary link: http://castaar.ws.webwow.be where you can see my code in action...

Watching for multiple elements without multiple if statements

I have a snippet that on scroll it checks wether an element is in the current viewport.
I now want to add multiple elements into the mix, but I wanted to avoid doing multiple if statements checking for each, I know the following code doesn't work but it is an example of how I would like to do it, is there a way of doing it this way?
var listOfPanels = $('#item2, #item2, #item3, #item4, #item5');
$(window).scroll(function(event) {
// if the element we're actually looking for exists
if (listOfPanels.length){
// check if the element is in the current view using the attached function
// and the event hasn't already fired
if (isElementInViewport(listOfPanels)) {
// do something
}
}
});
try this:
function isElementInViewport(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while(el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
var listOfPanels = $('#item2, #item2, #item3, #item4, #item5');
$(window).scroll(function(event) {
if (listOfPanels.length){
listOfPanels.each(function(){
if (isElementInViewport($(this)[0])) {
console.log($(this).attr('id') + ' in viewport');
}
});
}
});
(isElementInViewport js method brought from: How to tell if a DOM element is visible in the current viewport?)
hope that helps.

Adjust scroll position to a nearest wrapper div both upwards and downwards

I've read a bunch of threads but still can't come away with a solution that doesn't use a snap scroll plugin (https://github.com/wtm/jquery.snapscroll).
I was trying to follow this
function scrollTo(a){
//Get current scroll position
var current = (document.all ? document.scrollTop : window.pageYOffset);
//Define variables for data collection
var target = undefined;
var targetpos = undefined;
var dif = 0;
//check each selected element to see witch is closest
$(a).each(function(){
//Save the position of the element to avoid repeated property lookup
var t = $(this).position().top;
//check if there is an element to check against
if (target != undefined){
//save the difference between the current element's position and the current scroll position to avoid repeated calculations
var tdif = Math.abs(current - t);
//check if its closer than the selected element
if(tdif < dif){
//set the current element to be selected
target = this;
targetpos = t;
dif = tdif;
}
} else {
//set the current element to be selected
target = this;
targetpos = t;
dif = Math.abs(current - t);
}
});
//check if an element has been selected
if (target != undefined){
//animate scroll to the elements position
$('html,body').animate({scrollTop: targetpos}, 2000);
}
}
I'm trying to get it to scroll into view
<div class="projectWrap">
Fiddle: http://jsfiddle.net/Dar_T/2h2wjp2L/1/
much like how this site http://www.takumitaniguchi.com/tokyoblue/ has it scroll for its containers.
First you have to find the offset() of the element. Then comparing it to the $(window.scrollTop()), then you can do whatever changes you want. Here's some of the codes:
var project1 = $(".projectWrap").offset();
if($(window).scrollTop() >= project1.top){ // compare window scrolltop to element offset()
$("#tlos1").css("color","blue"); // change navigation
$("#tlos2").css("color","black");
$("#tlos3").css("color","black");
}
Here's the DEMO
Try this out:
var topoffset = 30;
function goTo(tagId) {
var destination = $( '#'+tagId ).offset().top - topoffset;
$("html:not(:animated),body:not(:animated)").animate( { scrollTop: destination}, speed);
});
And create urls with hash like this:
Go to section1

How to calculate an element's height considering collapsed margins

I want to calculate the total "height" of a div element considering the effect of collapsed margins because of child elements, that is, the total space that div element occupies in the document. I'm having a hard time thinking of the best algorithm / approach to do this.
Consider, for example, a div element with a margin-top of 10px and a height of 50px. This div element has a child <h2> element that has a margin-top of 20px. The div's margin will then collapse and the actual "height" of that div will be 70px. However, using jQuery methods, we are only able to get the height of the div without considering it's margins, or considering it's 10 pixel margin which would give us the wrong value:
$(elem).outerHeight() // 50
$(elem).outerHeight(true) // 60
To help illustrate my point, here is a jsfiddle I created with two examples.
My best guess at the moment is we have to iterate over all children of the div in some way and calculate the highest top and bottom margin.
According to what I understand from the W3C specification, we can skip this iteration for the top margin if the target div has a top-border-width or a top-padding. Ditto for the bottom margin.
Any help would be greatly appreciated. Thanks!
EDIT:
One (ugly) solution I thought about was wrapping the target div element in another div.
Then, we quickly add and remove a transparent borderTop and borderBottom to the wrapping div, measuring it's height in between. The borders will force the wrapping div's margin not to collapse with its children's margins. Something like this:
var collapsedHeight = function( target ) {
var $wrapper = $('<div />'),
$target = $(target);
$wrapper.insertAfter($target);
$target.appendTo($wrapper);
$wrapper.css({
borderTop: '1px solid transparent',
borderBottom: '1px solid transparent'
});
var result = $wrapper.outerHeight() - 2;
$target.insertAfter($wrapper);
$wrapper.remove();
return result;
};
I made a jsFiddle for it here.
Consider this hack:
$( elem ).wrap( '<div style="border:1px solid transparent;"></div>' ).parent().height()
The above expression returns 70 which is what you want, right?
So, the idea is to wrap your element in a DIV that has a transparent border set. This border will prevent the margins of your element to interfere with the margins of its previous and next sibling.
Once you get the height value, you can unwrap your element...
For a solution that doesn't involve DOM manipulation, you can achieve the same effect by adding padding to the element being measured and then removing it afterwards.
function getRealHeight(elementP) {
var
element = (elementP instanceof jQuery)? elementP : $(element),
padTop = parseInt(element.css('paddingTop')),
padBottom = parseInt(element.css('paddingBottom')),
offset,
height;
if (padTop == 0 || padBottom == 0) {
offset = 0;
if (padTop == 0) {
element.css('paddingTop', 1);
offset += 1;
}
if (padBottom == 0) {
element.css('paddingBottom', 1);
offset += 1;
}
height = (element.outerHeight(true) - offset);
if (padTop == 0) {
element.css('paddingTop', '');
}
if (padBottom == 0) {
element.css('paddingBottom', '');
}
} else {
height = element.outerHeight(true);
}
return height;
}
The bonus of this solution; you can sidestep the overhead of wrap/unwrap.
You can make it a jQuery plugin:
(function ($) {
$.fn.extend({
//plugin name - realHeight
realHeight: function (options) {
//Settings list and the default values
var defaults = {};
var options = $.extend(defaults, options);
function getRealHeight(elementP) {
var
element = (elementP instanceof jQuery) ? elementP : $(element),
padTop = parseInt(element.css('paddingTop')),
padBottom = parseInt(element.css('paddingBottom')),
offset,
height;
if (padTop == 0 || padBottom == 0) {
offset = 0;
if (padTop == 0) {
element.css('paddingTop', 1);
offset += 1;
}
if (padBottom == 0) {
element.css('paddingBottom', 1);
offset += 1;
}
height = (element.outerHeight(true) - offset);
if (padTop == 0) {
element.css('paddingTop', '');
}
if (padBottom == 0) {
element.css('paddingBottom', '');
}
} else {
height = element.outerHeight(true);
}
return height;
}
return getRealHeight($(this));
}
});
})(jQuery);

Categories