Hide or show navbar in ReactJS? - javascript

I was wondering how to change the navbar behavior based on the webpage position. I've seen a lot of tutorials about how to hide/show a navbar on scroll, which vie managed to implement successfully, however that seems to just cover two states. I would like my navbar to have three: show when I scroll up, hide when I scroll down, and change opacity if I'm at the top of the website. I've tried using a variety of useEffects and event listeners but I'm quite new to react and js.
Here's a good example of the effect I'm looking for https://brittanychiang.com/. As you can see once you are at the top of the webpage the opacity changes as well as the drop shadow, it all seems to be 'stuck' at the top instead of the floating look when you scroll up/down. Using dev tools I've found that the navbar class does seem to have three different names depending on the location of the scrollbar.
Thanks in advance!

use blank array in useEffect to run it once when mounted
then use one state to set as (top,down,up) from window scroll event
working demo
js
export function headerSystem(){
const [hState,sethState] = useState("top")
useEffect(()=>{
var lastVal = 0
window.onscroll = function(){
let y = window.scrollY
if(y > lastVal){sethState("down")}
if(y < lastVal) {sethState("up")}
if(y === 0) {sethState("top")}
lastVal = y
}
},[])
return (
<div className="root">
<div className={"header text-center p-4 bg-blue-200 fixed w-full text-lg font-semibold " + hState}>this is header</div>
<div className="pageContent bg-green-500">
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
<div className="each h-screen"></div>
</div>
</div>
)
}
css
.header {
background: rgba(0, 0, 0, 0);
transition: all 0.3s ease;
}
.top {
top: 0px;
}
.down {
top: -80px;
}
.up {
top: 0px;
}
.up,
.down {
background: rgba(0, 255, 242, 0.363);
box-shadow: 0px 0px 5px black;
padding: 8px;
}

You have to handle 'scroll' events using your component's lifecycle functions.
Please find this example for your header component. With some tweaks you may be able to use it.
Class component:
componentDidMount: function() {
window.addEventListener('scroll', this.handleScroll);
},
componentWillUnmount: function() {
window.removeEventListener('scroll', this.handleScroll);
},
handleScroll: function(event) {
let scrollTop = event.srcElement.body.scrollTop,
itemTranslate = Math.min(0, scrollTop/3 - 60);
this.setState({
transform: itemTranslate // and use this on your header
});
},
Functional component:
const [headerPosition, setHeaderPosition] = useState(0)
const handleScroll = useCallback((event) => {
let scrollTop = event.srcElement.body.scrollTop,
itemTranslate = Math.min(0, scrollTop/3 - 60);
setHeaderPosition({
transform: itemTranslate // and use this on your header
});
}, [setHeaderPosition])
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll])

Related

SlideToggle open only one container at a time Vanilla JS

Maybe someone know how to open only one container at a time? Now in this example you can open all three? I would like to open only one and when it's opened change text to "Close". Any ideas?
Here is the link with a code to codepen: code https://codepen.io/jorgemaiden/pen/YgGZMg
I'll be really apreciate for any help and tips!
You can do it in many ways, but according to your reference, I would just add function that loop through your elements which is not your clicked element, then remove active class if it's present
var linkToggle = document.querySelectorAll(".js-toggle");
for (i = 0; i < linkToggle.length; i++) {
linkToggle[i].addEventListener("click", function(event) {
event.preventDefault();
var container = document.getElementById(this.dataset.container);
this.innerText = "Close";
toggleSlide(container);
});
}
function toggleSlide(container) {
for (i = 0; i < linkToggle.length; i++) {
let el = document.getElementById(linkToggle[i].dataset.container);
if (el != container && el.classList.contains("active")) {
el.style.height = "0px";
linkToggle[i].innerText = "Click";
el.addEventListener(
"transitionend",
function() {
el.classList.remove("active");
}, {
once: true
}
);
}
}
if (!container.classList.contains("active")) {
container.classList.add("active");
container.style.height = "auto";
var height = container.clientHeight + "px";
container.style.height = "0px";
setTimeout(function() {
container.style.height = height;
}, 0);
} else {
container.style.height = "0px";
container.addEventListener(
"transitionend",
function() {
container.classList.remove("active");
}, {
once: true
}
);
}
}
.box {
width: 300px;
border: 1px solid #000;
margin: 10px;
cursor: pointer;
}
.toggle-container {
transition: height 0.35s ease-in-out;
overflow: hidden;
}
.toggle-container:not(.active) {
display: none;
}
<div class="box">
<div class="js-toggle" data-container="toggle-1">Click</div>
<div class="toggle-container" id="toggle-1">I have an accordion and am animating the the height for a show reveal - the issue is the height which i need to set to auto as the information is different lengths.<br><br> I have an accordion and am animating the the height fferent lengths.
</div>
</div>
<div class="box">
<div class="js-toggle active" data-container="toggle-2">Click</div>
<div class="toggle-container open" id="toggle-2">I have an accordion and am animating the the height for a show reveal - the issue is the height which i need to set to auto as the information is different lengths.<br><br> I have an accordion and am animating the the height fferent lengths.
</div>
</div>
<div class="box">
<div class="js-toggle" data-container="toggle-3">Click</div>
<div class="toggle-container" id="toggle-3">I have an accordion and am animating the the height for a show reveal - the issue is the height which i need to set to auto as the information is different lengths.<br><br> I have an accordion and am animating the the height fferent lengths.
</div>
</div>

When scrolling flashing background color

My code is to apply div background-color when user touchstart on each div. that's clear for you.
Then when user scrolling, I use $(window).scroll.. event to reset or remove background-color for all div's.
But my problem is: when user (touch + scrolling) at div's, background-color is flashing out color! I want to: don't change div background-color when (tap, touch + scrolling). if you don't understand my question, you can see: (facebook messenger friend conversations, snapchat conversations, stories ..etc)
Flashing div's:
My code: https://www.w3schools.com/code/tryit.asp?filename=GLUIAUU64MDX - RUN ON TOUCH DEVICES.
$(window).scroll(function() {
resetBg();
});
$(".❄").on('touchstart', function() {
$(this).css("background-color", "#7bffea");
});
$(".❄").on('touchend mouseleave mouseup blur click', function() {
resetBg();
});
function resetBg() {
$(".❄").css("background-color", "");
}
div.❄ {
display: block;
height: 150px;
border: 1px solid black;
margin: 10px 0px;
padding: 0px;
border-radius: 8px;
background-color: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
<div class="❄"></div>
Instead of setting background on touchstart, you can get the pointer position at touchstart and on touchend check if pointer position is changed or not. If not, change the div background and set a timeout function to reset it. If the position of pointer is changed then it's a scroll so you don't need to change the background
let initialY = null;
$(".❄").on('touchstart', function(e) {
initialY = e.clientY;
});
$(".❄").on('touchend', function(e) {
if(e.clientY===initialY){
$(this).css("background-color", "#7bffea");
setTimeout(()=>{ //This is optional
resetBg();
},100);
}
});
function resetBg() {
$(".❄").css("background-color", "");
}
try this
$(window).scroll(function() {
resetBg();
});
//----------------------------------------------
$(document).on('touchstart', '.❄', function(evt) {
var that = this;
var oldScrollTop = $(window).scrollTop();
window.setTimeout(function() {
var newScrollTop = $(window).scrollTop();
if (Math.abs(oldScrollTop - newScrollTop) < 3) $(that).css("background-color", "#7bffea");
}, 200);
});
$(".❄").on('touchend touchmove mouseleave mouseup blur click', function() {
resetBg();
});
function resetBg() {
$(".❄").css("background-color", "");
}

How to Delay a Javascript Function Until it is in the middle of web page?

Hello I have a number animation on my web page and I dont want the animation to start until it is in the middle of the web page. I tried to google onscroll and other options but I could not get this to work properly.
I prefer for the animation not to start until the visitor has scrolled down to 472px. As of right now as soon as the web page loads the number animation starts automatically. Any help I would really appreciate it.
// 472 px - Starts Yellow Counter Section
const counters = document.querySelectorAll('.counter');
const speed = 200; // The lower the slower
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
// Lower inc to slow and higher to slow
const inc = target / speed;
// console.log(inc);
// console.log(count);
// Check if target is reached
if (count < target) {
// Add inc to count and output in counter
counter.innerText = count + inc;
// Call function every ms
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
};
updateCount();
});
.bg-yellow-white {
background: #f7c51e;
color: white;
}
.container {
max-width: 1404px;
margin: auto;
padding: 0 2rem;
overflow: hidden;
}
.l-heading {
font-weight: bold;
font-size: 4rem;
margin-bottom: 0.75rem;
line-height: 1.1;
}
/* Padding */
.py-1 {
padding: 1.5rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* All Around Padding */
.p-1 {
padding: 1.5rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
/* ======================== Red Block ======================== */
.red-block {
height: 472px;
width: 100%;
background-color: red;
}
/* ======================== PROJECS COMPLETED ======================== */
#projects-completed .container .items {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
#projects-completed .container .items .item .circle {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
<div class="red-block">
<p>red block</p>
</div>
<section id="projects-completed" class="counters bg-yellow-white">
<div class="container">
<div class="items">
<div class="item text-center p-3">
<div class="circle">
<div class="counter l-heading" data-target="1750">500</div>
</div>
<h2 class="py-2">Projects Completed</h2>
</div>
<div class="item text-center p-3">
<div class="circle py-2">
<div class="l-heading counter" data-target="5">500</div>
</div>
<h2 class="py-2">Staff Members</h2>
</div>
<!-- <div class="item text-center p-3">
<div class="circle">
<h3 class="l-heading ">1750</h3>
</div>
<h2 class="py-2">Projects Completed</h2>
</div>
<div class="item text-center p-3">
<div class="circle py-2">
<h3 class="l-heading">5</h3>
</div>
<h2 class="py-2">Staff Members</h2>
</div> -->
</div>
</div>
</section>
wesbos has great video on this https://www.youtube.com/watch?v=uzRsENVD3W8&list=PLu8EoSxDXHP6CGK4YVJhL_VWetA865GOH&index=14&t=0s
Basically what you need to do is listen for scroll and check where user currently is compared to desired place in px
you can check code here and adjust it to your needs https://github.com/wesbos/JavaScript30/blob/master/13%20-%20Slide%20in%20on%20Scroll/index-FINISHED.html
Try getBoundingClientRect(). document.querySelector( 'some element' ).getBoundingClientRect() will give you the properties of the specific element
for Example if you want to start an animation when an element is visible to user on his screen ( in the visible viewport ), you can use this to call the function and start the animation
let calledStatus = 0; // some flag variable to remember if function is called
window.onscroll = function(){
element = document.querySelector( '.some element' );
clientRect = element.getBoundingClientRect();
if( clientRect.top < window.innerHeight && clientRect.top > ( clientRect.height * -1) && calledStatus == 0){
//call your function or do other stuff
console.log('called' )
calledStatus = 1;
}
}
By using jquery , first add this reference script above your js code or referenece script
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></scrip>
....
</head>
if you want the code to launch specifically after 472 px:
js
$(document).ready(function () {
Let initialScroll = true;
//you can decrease or increase 472 depending on where exactly
//you want your function to be called
$(document).scroll(function () {
if (($(document).scrollTop() > 472)&& initialScroll) {
//call your function here
console.log( "reached 472")
InitialScroll=false;
}
});
});
if you want your function to start after reaching the middle
of the document
you place a div where the middle of the html code is :
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
...
<div id="middle"></div>
...
</body>
</html>
js
$(document).ready(function () {
Let initialScroll=true
$(document).scroll(function () {
if (($(document).scrollTop() >=$('#middle').position().top)&&initialScroll) {
//call your function here
console.log( "reached middle")
InitialScroll=false;
}
});
});
There is a native javascript API for "listetning" where the user currently is on the page called Intersection Observer. Basically you set a callback which should execute once the desired content scrolls into view.
It's used for all those fancy page animations where cards appear once you start scrolling to the bottom of the page since it's far more efficient than listening on the scroll event.
Kevin Powell did a great video about this topic.
Hope it helps!
Here's a code copy pasted, but it should give you a clue on how it should work:
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
let active = false;
const lazyLoad = function() {
if (active === false) {
active = true;
setTimeout(function() {
lazyImages.forEach(function(lazyImage) {
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImages = lazyImages.filter(function(image) {
return image !== lazyImage;
});
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
}
});
active = false;
}, 200);
}
};
document.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
window.addEventListener("orientationchange", lazyLoad);
});

How do I create an infinitely(on x axis) scrollable element that repeats it content

I have created a div that can be scrolled with a mouse just like on mobile, i.e you click on the container, move your mouse and the element gets scrolled.
Nothing fancy, here is the link to codepen: https://codepen.io/kulaska/pen/xxKdRGw
Here is the HTML structure:
<div class="container">
<div class="child"></div>
...
<div class="child"></div>
</div>
This is the main chunk of JS file:
container.addEventListener("mousemove", ({clientX: newX}) => {
if (!firing) return;
let newPos = currPos - (newX - prevX);
let availableOffset = container.scrollWidth - container.clientWidth;
if (newPos > availableOffset)
newPos = availableOffset;
if (newPos < 0)
newPos = 0;
prevX = newX;
container.scrollTo(newPos, 0);
currPos = newPos;
})
I use flex container to create a container that doesn't wrap the content:
.container {
display: flex;
flex-wrap: nowrap;
..
}
Imagine that there are 20 images in that container. The question is how would you make this div infinitely scrollable like when you scroll it till the end(till the picture number 20 shows up) and then the picture number 1,2,3,4 etc after it, so all the content is repeated.
And it can go on and on and on, so just putting a lot of DOM nodes in the container is not an option, because it would be too costly in terms of performance.
I thought of a few JS solutions, but they are all pretty bad for performance. How would you solve this?
I did some edits on your codepen and i came up with this solution. I just added a few lines of code to your mousemove event. You already check if you reach the start or the end of the container, right? So let's extend those adding just a couple of lines of code which does as follows:
when you try to go lower than zero (so, let's say from your child with index 0 to -1) you just cut the last-child element of the container and paste before the first.
Same thing when you reach the end of the container (let's say, element with index 19 and you try to reach for the 20)
here's the code i edited:
if (newPos > availableOffset){
elementToCut = container.querySelector('.child:first-child')
container.appendChild(elementToCut)
newPos = availableOffset - elementToCut.offsetWidth;
elementToCut.removeChild
}
if (newPos < 0){
firstElement = container.querySelector('.child:first-child')
elementToCut = container.querySelector('.child:last-child')
container.insertBefore(elementToCut,firstElement);
newPos = 0 + elementToCut.offsetWidth;
elementToCut.removeChild
}
then you add or remove (depends if you're scrolling to the start or to the end) the width of the "moved" element to your current newPos. Let me know if it fits your needs or if helped !!
edit: i'd create a couple of functions to make some order in this code (for example, the code which cuts&pastes the elements)
const container = document.querySelector(".container");
const children = document.querySelector(".child");
let [firing, currPos, prevX] = [false, 0, 0];
container.addEventListener("mousedown", e => {
prevX = e.clientX;
e.preventDefault();
firing = true;
});
container.addEventListener("mousemove", ({
clientX: newX
}) => {
if (!firing) return;
let newPos = currPos - (newX - prevX);
let availableOffset = container.scrollWidth - container.clientWidth;
if (newPos > availableOffset) {
elementToCut = container.querySelector('.child:first-child')
container.appendChild(elementToCut)
newPos = availableOffset - elementToCut.offsetWidth;
elementToCut.removeChild
}
if (newPos < 0) {
firstElement = container.querySelector('.child:first-child')
elementToCut = container.querySelector('.child:last-child')
container.insertBefore(elementToCut, firstElement);
newPos = 0 + elementToCut.offsetWidth;
elementToCut.removeChild
}
prevX = newX;
container.scrollTo(newPos, 0);
currPos = newPos;
})
container.addEventListener("mouseup", () => {
firing = false;
})
container.addEventListener("mouseleave", () => {
firing = false;
})
.container {
display: flex;
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
}
.container::-webkit-scrollbar {
display: none;
}
.child {
flex: 1 0 200px;
height: 200px;
margin: 10px;
}
.green {
background: green;
}
.blue {
background: blue;
}
<div class="container">
<div class="child green">1</div>
<div class="child blue">2</div>
<div class="child green">3</div>
<div class="child blue">4</div>
<div class="child green">5</div>
<div class="child blue">6</div>
<div class="child green">7</div>
<div class="child blue">8</div>
<div class="child green">9</div>
<div class="child blue">10</div>
<div class="child green">11</div>
<div class="child blue">12</div>
<div class="child green">13</div>
<div class="child blue">14</div>
</div>

Loading spinner VueJS

I have to make a loading animation when a client clicks the button search to popup a spinner animation, in order the client can't click multiple times on the search button. However, I don't know how to call this animation. I have made this until now:
table.vue:
<div id="overlay-back"></div>
<div id="overlay">
<div id="dvLoading">
<img id="loading-image" src="../assets/images/spinner.gif" alt="Loading..."/>
</div>
</div>
loadData(filter) {
var self = this;
const url = this.$session.get('apiUrl') + 'loadSystemList'
this.submit('post', url, filter);
}
main.css:
#overlay {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
z-index : 995;
display : none;
}
#overlay-back {
position : absolute;
top : 0;
left : 0;
width : 100%;
height : 100%;
background : #000;
opacity : 0.6;
filter : alpha(opacity=60);
z-index : 990;
display : none;
}
#dvLoading {
padding: 20px;
background-color: #fff;
border-radius: 10px;
height: 150px;
width: 250px;
position: fixed;
z-index: 1000;
left: 50%;
top: 50%;
margin: -125px 0 0 -125px;
text-align: center;
display: none;
}
I need to call the animation when the button Search is clicked and invokes the function loadData. I would be happy if you help me guys :) I am kinda lost
Update1:
file.vue:
<template>
<div>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css" integrity="sha384-O8whS3fhG2OnA5Kas0Y9l3cfpmYjapjI0E4theH4iuMD+pLhbf6JI0jIMfYcK3yZ" crossorigin="anonymous">
<div id="dvLoading">
<i class="fa fa-spinner fa-spin fa-10x"></i>
</div>
<div class="toolbarStrip">
<br><h1 style="text-align: center; padding-bottom: 10px;">System table</h1>
<fieldset class="buttons">
<span class="logInBTN" v-on:click="loadData(filter)" id="loadData">Search</span>
</fieldset>
</div>
</div>
</template>
<script type="text/javascript">
import config from '../main.js'
var loadButton = document.getElementById("loadData");
export default {
data(){
return {
},
methods: {
stopShowingLoading(){
var element = document.getElementById("dvLoading");
element.classList.remove("showloading");
var button = document.getElementById("loadData");
button.classList.remove("showloading");
},
loadData(filter) {
var element = document.getElementById("dvLoading");
element.classList.add("showloading");
var button = document.getElementById("loadData");
button.classList.add("showloading");
var self = this;
const url = this.$session.get('apiUrl') + 'loadSystemList'
this.submit('post', url, filter);
window.setTimeout(function(){stopShowingLoading();},3000);
},
submit(requestType, url, submitData) {
this.$http[requestType](url, submitData)
.then(response => {
this.items = response.data;
})
.catch(error => {
console.log('error:' + error);
});
},
newData: function(){
config.router.push('/systemData')
}
}
}
</script>
first of all, whilst I have done things with vue.js in the past, I've forgotten much of that so there may be a better way within that framework than this, which is a vanilla JS approach really...
You don't seem to have a requirement to stop showing the loading animation. When I've done this sort of thing in the past, I've usually made use of callbacks to know when the loading operation is complete, and at that point 'turn off' the loading animation. I've included a function to hide the loading, but don't know where/if you'd want to call this.
This is untested, so apologies for typos or other minor errors...
css:
/*
Override the display:none on the #dvloading element if it has a class
of 'showloading
*/
#dvLoading.showloading{
display:block
}
JS:
function loadData(filter) {
/*
Add the 'showloading' class to the #dvLoading element.
this should make it appear due to the css change...
*/
var element = document.getElementById("dvLoading");
element.classList.add("showloading");
var self = this;
const url = this.$session.get('apiUrl') + 'loadSystemList'
this.submit('post', url, filter);
}
function stopShowingLoading(){
/*
When loading finishes, reverse the process
*/
var element = document.getElementById("dvLoading");
element.classList.remove("showloading");
}
edit: jsFiddle to show general approach
further edit: To stop showing animation only after data has loaded (I just used a timeout to simulate this in my example) then you need to simply stop it after the data has loaded, which would be something like this:
submit(requestType, url, submitData) {
this.$http[requestType](url, submitData)
.then(response => {
// We've received the data now, so set items and
//also hide the loading animation.
this.items = response.data;
this.stopShowingLoading();
})
...
and then remove the window.setTimeout() call altogether.

Categories