add className on scroll in JavaScript - javascript

I'm trying to add a className on scroll. I keep getting a
document is undefined
edit: I found out I was getting the error from the typo. When I define document.getElementsByClassName("main-nav").scrollTop nothing comes up in the console. As well as the page does not get affected.
window.onscroll = function() {
windowScroll();
};
function windowScroll() {
if (document.getElementsByClassName("main-nav").scrollTop > 50 || document.documentElement.scrollTop > 50) {
document.getElementsByClassName("main-nav").className = "test";
} else {
document.getElementsByClassName("main-nav").className = "";
}
}
CSS is
.test {
background: pink
}
I'm not necessarily looking for the answer, I just want guidance

There are 2 problems:
getElementsByClassName returns an array of HTMLCollection and it has no property scrollTop. You probably want the first item so the code shoul be document.getElementsByClassName("main-nav")[0] (or document.querySelector(".main-nav"))
But if you try it, you will get an error:
Cannot read property 'scrollTop' of undefined
window.onscroll = function() {
windowScroll();
};
function windowScroll() {
if (document.getElementsByClassName("main-nav").scrollTop > 50 || document.documentElement.scrollTop > 50) {
document.getElementsByClassName("main-nav").className = "test";
} else {
document.getElementsByClassName("main-nav").className = "";
}
}
html, body {
height: 150%;
}
.test {
background: pink
}
<div class="main-nav"></div>
The reason is that you override the class attribute of .main-nav by this assignment:
document.getElementsByClassName("main-nav").className = "";
In this line you set the class attribute to empty string. You probably want to add / remove the test call but keeping the main-nav class.
There are 2 things you can do:
Set the id attribute to main-nav instead of the class attribute, then use document.getElementById method.
window.onscroll = function() {
windowScroll();
};
function windowScroll() {
if (document.getElementById("main-nav").scrollTop > 50 || document.documentElement.scrollTop > 50) {
document.getElementById("main-nav").className = "test";
} else {
document.getElementById("main-nav").className = "";
}
}
html, body {
height: 150%;
}
#main-nav {
position: fixed;
width: 100%;
}
.test {
background: pink
}
<div id="main-nav">Main Nav</div>
Toggle only the test class using classList.toggle.
window.onscroll = function() {
windowScroll();
};
function windowScroll() {
if (document.getElementsByClassName("main-nav")[0].scrollTop > 50 || document.documentElement.scrollTop > 50) {
document.getElementsByClassName("main-nav")[0].classList.add("test");
} else {
document.getElementsByClassName("main-nav")[0].classList.remove("test");
}
}
html, body {
height: 150%;
}
.main-nav {
position: fixed;
width: 100%;
}
.test {
background: pink
}
<div class="main-nav">Main Nav</div>
The final approach with some optimisations:
var mainNav = document.querySelector('.main-nav');
window.onscroll = function() {
windowScroll();
};
function windowScroll() {
mainNav.classList.toggle("test", mainNav.scrollTop > 50 || document.documentElement.scrollTop > 50);
}
html, body {
height: 150%;
}
.main-nav {
position: fixed;
width: 100%;
}
.test {
background: pink
}
<div class="main-nav">Main Nav</div>
The changes:
Store the .main-nav element on the global context (the window object). It will not change so you don't need to find it in any scroll.
Use querySelector so you will get a single DOM element, not collection.
Use classList.toggle to toggle the class by condition.

The issue with your console.log is that you're trying to pull the scrollTop for an HTML Collection (a collection of elements in your page) of 1 or more divs - therefore it can't check for the scrollTop as the console.log as it doesn't actually have that property.
Assuming you only have one element with the "main-nav" class (or there is a particular element with this class that you wish to apply it to), you would be better off using one of the following: document.getElementsByClassName("main-nav")[0] or document.getElementById("main-nav") (the latter would require you to create a main-nav id rather than a class).
For the first one, however, using className reassigns the class name rather than adding to that particular div, therefore you can use document.getElementsByClassName("main-nav")[0].classList.add("test") (and remove instead of add if it does not match your criteria).
If there is more than one element with the "main-nav" class, you can still use the first option I suggested - only you would need to wrap it around in a for loop and replace the 0 with your variable of choice.
for (i = 0; i < document.getElementsByClassName("main-nav").length; i++) {
//your code here using document.getElementsByClassName("main-nav")[i]
}

Related

How to activate different functions if you are at certain part of the page?

I want to make two functions, "functionattoppage" and "functionat1000pxpage". I want to make the function "functionattoppage" activate when the user is at the top of a webpage and "functionat1000pxpage" activate when the user is down about 1000px from the top of the page, is this possible?
Here is the only thing I could come up with:
window.onscroll = function () {
scrollFunction()
};
function scrollFunction() {
if (document.body.scrollTop > 1000 || document.documentElement.scrollTop > 1000) {
document.body.classList.add("body.changed");
}
if (document.body.scrollTop > 0 || document.documentElement.scrollTop > 0) {
document.body.classList.remove("body.changed");
}
}
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
Sorry if I didn't explain this very well, I don't have much time right now. If you need more information please ask me.
After console logging document.body.scrollTopin the fiddle came out to be zero. Plus your conditions are bound to fail because when scrollTop > 1000 then it is > 0 too.
you should either make it a else or move the >0 condition to the first
window.onscroll = function() {
scrollFunction()
};
function scrollFunction() {
console.log(document.documentElement.scrollTop);
if (document.documentElement.scrollTop > 200) {
document.getElementsByTagName('h1')[1].classList.add("changed");
}
else{
document.getElementsByTagName('h1')[1].classList.remove("changed");
}
}
.changed {
color: red
}
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
<div style="height: 400px;background: grey;"></div>
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
<div style="height: 400px;background: grey;"></div>
Looks like you are trying to hide a class when the scroll position for the window is within 0 and 1000 pixels yes? The a conditional that will check if the scrollY position of the window, window.scrollY is greater than 0, but less than 1000, add the class else remove the class.
window.onscroll = () => {
scrollFunction()
}
const scrollFunction = () => {
window.scrollY > 0 && window.scrollY < 1000 ?
document.body.classList.add("changed") :
document.body.classList.remove("changed")
document.getElementById("scroll_position").textContent = window.scrollY;
}
#scroll_position {
position: fixed;
top: 0;
right: 0;
background: red;
color: #fff;
padding: 8px;
width: 100px;
}
#cont {
height: 3000px;
}
#cont h1 {
position: sticky;
top: 50px;
}
.changed {
background: black;
color: white;
}
<p id="scroll_position"></p>
<div id="cont">
<h1>Please look at the JavaScript section, it is what my best guess would be on how to make this work</h1>
</div>
You can use a div as your marker. And when it is >= 0 execute one function and when it gets to -1000px execute another. Use getBoundingClientRect() to get an elements coordinates.
HTML
<div id=“marker1”></div>
JS
window.addEventListener( "scroll", function(){
let marker1 = document.getElementById("marker1").getBoundingClientRect();
if (marker1.top >= 0) atTop();
if (marker1.top <= -1000) at1000px();
})
function atTop() {
document.body.style.backgroundColor = "blue";
}
function at1000px() {
document.body.style.backgroundColor = "red";
}
Be aware the atTop won’t execute until you start scrolling because of the type of event listener. If you want it executed on load also then you’ll need to add that.
You can do it through sensing the scroll event as you are doing, but it's quite overheady.
Instead you could use an IntersectionObserver on a couple of elements, This will tell you when they come into and go out of the viewport and you don't have to worry about any intermediate scrolling.
If you already have elements that you want to sense in those positions you could sense those going into and out of the viewport. If not you can 'plant' a couple of 1px divs at the top and at 1000px and sense them coming in and out.
This snippet just logs the comings and goings to the console but of course you put whatever code you want in their place.
function callback (entries) {
for (let i = 0; i < entries.length; i++) {
if (entries[i].isIntersecting) {console.log(entries[i].target.classList + ' is in the viewport'); }
else {console.log(entries[i].target.classList + ' is not in the viewport'); }
}
}
const observer = new IntersectionObserver(callback);
const sensors = document.querySelectorAll('.sensor');
for (let i = 0; i < sensors.length; i++) {
observer.observe(sensors[i]);
}
.talldiv {
width: 100vw;
height: 2000px;
background-image: linear-gradient(to bottom, red, blue, orange, green, purple, cyan); /*just so we notice scrolling */
}
.sensor {
position: absolute;
left: 50%;
top: var(--top);
width: 1px;
height: 1px;
background: transparent;
}
.sensetop {
--top: 0px;
}
.sense1000px {
--top: 999px;
}
<div class="sensor sensetop"></div>
<div class="sensor sense1000px"></div>
<div class="talldiv"></div>

(HTML/Js) Cycling element visibilities

var terminal = document.getElementById('terminal');
var vncScreen = document.getElementById('screen');
var video = document.getElementById('video');
var vncToggle = document.getElementById('vncToggle');
var termToggle = document.getElementById('terminalToggle');
termToggle.onclick = function toggleTerminal() {
if (terminal.classList.contains('hide')) {
terminal.classList.remove('hide');
if (vncScreen.classList.contains('hide')) {} else {vncScreen.classList.add('hide')}
if (video.classList.contains('hide')) {} else {video.classList.add('hide')}
} else {
terminal.classList.add('hide');
if (video.classList.contains('hide')) {video.classList.remove('hide')} else {}
}
}
vncToggle.onclick = function toggleVNC() {
if (vncScreen.classList.contains('hide')) {
vncScreen.classList.remove('hide');
if (terminal.classList.contains('hide')) {} else {terminal.classList.add('hide')}
if (video.classList.contains('hide')) {} else {video.classList.add('hide')}
} else {
vncScreen.classList.add('hide');
if (video.classList.contains('hide')) {video.classList.remove('hide')} else {}
}
}
.black-box {
background: black;
height: 500px;
width: 500px;
position: absolute;
}
.green-box {
background: green;
height: 500px;
width: 500px;
position: absolute;
}
.blue-box {
background: blue;
height: 500px;
width: 500px;
position: absolute;
}
.hide {
display: none;
}
<button class="button" id="terminalToggle" title="Toggle Terminal">Toggle terminal</button>
<button class="button" id="vncToggle" title="Toggle Terminal">Toggle vnc</button>
<div id='video' class="black-box"></div>
<div id='screen' class="green-box hide"></div>
<div id='terminal' class="blue-box hide"></div>
basically when you click "Toggle terminal" it should show blue and then if you click again go back to black; when you click "Toggle vnc" it should show green and then if you click again go back to black. If you click "Toggle vnc" and it is already blue, it should turn green and vice versa (but clicking "Toggle terminal")
I currently have the following Js:
var terminal = document.getElementById('terminal'); //video-like element
var vncScreen = document.getElementById('screen'); //video-like element
var video = document.getElementById('video'); //video-like element
var vncToggle = document.getElementById('vncToggle'); //button
var termToggle = document.getElementById('terminalToggle'); //button
termToggle.onclick = function toggleTerminal() {
terminal.classList.toggle('hide');
vncScreen.classList.toggle('hide');
video.classList.toggle('hide');
}
vncToggle.onclick = function toggleVNC() {
vncScreen.classList.toggle('hide');
terminal.classList.toggle('hide');
video.classList.toggle('hide');
}
and css:
.hide {
display: none;
}
When I had just two different HTML elements, this class toggling methodology worked. Now that there are 3, I'm not sure it will work as desired.
video is initially visible i.e. hide is not in its classList
terminal is initially hidden i.e. hide is in its classList
vncScreen is initially hidden i.e. hide is in its classList
When toggleTerminal() is called:
video becomes hidden
terminal becomes visible
vncScreen becomes visible (but it should not)
If toggleVNC() is called (after toggleTerminal()):
video becomes visible again (but it should not)
terminal becomes hidden
vncScreen becomes hidden
Note how if the either of the function calls were toggled only by themselves, this method would work (provided I removed vncScreen.classList.toggle('hide'); in toggleTerminal() and terminal.classList.toggle('hide'); in toggleVNC()).
The problem is I need to account for any order of button-presses of termToggle and vncToggle. Essentially my goal is to "cycle" these elements such that:
1) Toggling of the "selected" element (i.e. termToggle corresponds to visibility of terminal element && vncToggle corresponds to visibility of vncScreen element) hides the remaining two elements (video && vncScreen || terminal && video respectively)
2) The order of toggling of "selected" elements does not affect 1)
3) A second toggle of the "selected" element will hide itself and the other element that is not video
Any ideas on how to best accomplish this?
At one point I thought about doing some logic that evaluated whether hide was contained in the appropriate classList's and just manually add or remove the hide class accordingly but this seemed kind of sloppy to me (idk, maybe its not?).
See code snippet in question for functionality, Js redundantly posted here:
var terminal = document.getElementById('terminal');
var vncScreen = document.getElementById('screen');
var video = document.getElementById('video');
var vncToggle = document.getElementById('vncToggle');
var termToggle = document.getElementById('terminalToggle');
termToggle.onclick = function toggleTerminal() {
if (terminal.classList.contains('hide')) {
terminal.classList.remove('hide');
if (vncScreen.classList.contains('hide')) {} else {vncScreen.classList.add('hide')}
if (video.classList.contains('hide')) {} else {video.classList.add('hide')}
} else {
terminal.classList.add('hide');
if (video.classList.contains('hide')) {video.classList.remove('hide')} else {}
}
}
vncToggle.onclick = function toggleVNC() {
if (vncScreen.classList.contains('hide')) {
vncScreen.classList.remove('hide');
if (terminal.classList.contains('hide')) {} else {terminal.classList.add('hide')}
if (video.classList.contains('hide')) {} else {video.classList.add('hide')}
} else {
vncScreen.classList.add('hide');
if (video.classList.contains('hide')) {video.classList.remove('hide')} else {}
}
}

jQuery animate scrollTop goes up then back down

I have a function that checks if there are any errors on the page and auto scrolls to them. The problem that I'm having is it scrolls up to them but then comes back down to where it was before. I'd like it to scroll up and stay there.
$(".submit_button").click(function (event) {
event.preventDefault();
var errorElements = $(".error").filter(":visible");
if (errorElements.size() > 0) {
target_top = $(errorElements).offset().top;
$('html, body').animate({
scrollTop: target_top
}, 800);
}
return false;
});
The problem is in your selector. I know why it's done. Any web dev that's been in this long enough has been using that for as much cross browser compat as possible, and yet still encountered this issue. The problem is, you're calling animate:scroll on 2 items consecutively using this selector.
The better way, in short, would be to check if it is a WebKit browser or not. Reason being is that non-WebKit tend to use html whereas WebKit browsers tend to use body (and sometime html). This can cause such confusion as you face now.
The simple short term solution is to use something like /WebKit/i.test(navigator.userAgent) in your click callback. This will help you assign only one selector to the animate call.
Example
var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html';
$(selector).animate( // ...
Test Snippet
$(function() {
// simply to make filler divs for scrolling
for (var i=0;i<10;i++) $('<div />', { 'id': 'div'+i, 'style': 'background-color: '+String.randColorHex()+';' }).append($('.temp').clone().removeClass('temp')).height($(window).height()).appendTo($('body'));
/*------------------------------------------*/
/***S*O*L*U*T*I*O*N***/
var divID = 0;
function btnCheck() { // IGNORE, simply to turn buttons on and off when reaching end
$('#btnScrollDown').prop('disabled', divID>=9);
$('#btnScrollUp').prop('disabled', divID<=0);
}
$(document)
.on('click', '#btnScrollDown', function() {
if (divID < 10) {
divID++;
// broke everything down so it's easy to see. You can shorten this in a few ways.
/*THIS HERE-> */var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html',
scrollSelector = '#div' + (divID),
scrollTop = $(scrollSelector).offset().top
props = { scrollTop: scrollTop },
time = 800;
$(selector).animate(props, time);
// simply to turn buttons on and off when reaching end
btnCheck();
}
})
.on('click', '#btnScrollUp', function() {
if (divID > 0) {
divID--
// broke everything down so it's easy to see. You can shorten this in a few ways.
/*THIS HERE-> */var selector = /WebKit/i.test(navigator.userAgent) ? 'body' : 'html',
scrollSelector = '#div' + (divID),
scrollTop = $(scrollSelector).offset().top
props = { scrollTop: scrollTop },
time = 800;
$(selector).animate(props, time);
// simply to turn buttons on and off when reaching end
btnCheck();
}
});
});
html, body, div { margin: 0; padding: 0; width: 100%; }
.buttons { display: inline-block; left: 1em; position: fixed; text-align: center; top: 1em; }
button { margin: .25em; padding: .1em .3em; width: 100%; }
.temp { dislpay: none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/JDMcKinstry/String.randColorHex/0c9bb2ff/String.randColorHex.js"></script>
<section class="buttons">
<button id="btnScrollUp" disabled>Scroll To Next Div Up</button><br />
<button id="btnScrollDown">Scroll To Next Down</button>
<sub><i>this isn't fully managed, only use buttons to scroll!</i></sub>
</section>
<table class="temp"><tr><td></td></tr></table>

Change Element style with window height with JavaScript

I'm trying to change the background colour of a div element when the page window height is lower than a certain value using JavaScript. I have a vague idea of how this might work but it doesn't seem to be having any effect.
This is within a tag and loads after the body of my HTML.
<script>
var footer = document.getElementByClassName("footer");
if (window.innerHeight < 800) {
footer.style.backgroundColor = 'black';
}
</script>
Like already mentioned getElementsByClassName returns an array. You had a typo there so nothing was returned, however.
As long as there's only 1 element with the class name "footer" this should work (might be better to use an ID):
<script>
var footer = document.getElementsByClassName("footer")[0];
if (window.innerHeight < 800) {
footer.style.backgroundColor = 'black';
}
</script>
But yeah, you should really look at the dev console when you're writing Javascript. It can save a lot of headaches!
JavaScript does not have a method called getElementByClassName.
The closest thing is getElementsByClassName which returns an HTMLCollection which is an array-like object, so to get the first element in the list you have use an index:
var footer = document.getElementsByClassName('footer')[0];
You can also use querySelector to get the first element with the class name:
var footer = document.querySelector('.footer');
Here is a working example:
var footer = document.getElementsByClassName('footer')[0];
window.onresize = function (event) {
if (window.innerHeight < 800) {
footer.style.backgroundColor = 'black';
} else {
footer.style.backgroundColor = 'blue';
}
}
.footer {
height: 50px;
width: 100%;
background: black;
}
<footer class="footer"></footer>
JSFiddle Demo http://jsfiddle.net/moogs/x63rc6v4/1/

Using prototype with scrollTop to make instances of a Class(js class) active (css class)

I think the title is pretty confused, but i dont know how to named what i'm trying to do.
I have a paragraph:
<p data-initial="100" data-final="1000">Test</p>
NOTE THE DATA-*
And this paragraph have a simple CSS:
p {
color: black;
margin-top: 500px;
}
p.active {
color: red;
}
And this paragraph is an instance of Animation class:
+ function($) {
var Animation = function(element) {
this.element = $(element);
this.initial = this.element.data('initial');
this.final = this.element.data('final');
if (this.initial > $(this).scrollTop()) {
this.applyAnimation();
}
}
Animation.prototype.applyAnimation = function() {
alert('WORKED!!');
this.element.addClass('active');
}
Animation.prototype.disableAnimation = function() {
this.element.removeClass('active');
}
$(window).on('load', function() {
$('[data-initial]').each(function() {
var animation = $(this);
animation = new Animation(this);
});
});
}(jQuery);
With this code, i'm trying to apply the .active class in the paragraph if the page scroll more them 100, but is not working at all, not happens.
I think maybe it's because I'm trying to 'hear' the scroll inside the prototype , it is not possible? How can I make my instance hear the scroll and apply the class when the page scroll over 100 ??
If i do this:
$(window).scroll(function() {
if ($(this).scrollTop() > 100) {
console.log('test');
}
});
The test will appear in my console, so, the window.scroll code is not wrong.
I think this line might cause a problem:
if (this.initial > $(this).scrollTop()) {
"this" will be the new instance of Animation which isn't an element. Maybe that should be this.element.scrollTop()?

Categories

Resources