I've built a navigation that hides whenever the user scrolls down and comes back into view when the user scrolls up. This works perfectly on Chrome and Safari but not on Firefox. On Firefox the navigation does not do anything except when I hover over it with the inspect element tool.
Running React 16.9. I've tried the following things.
Tested on newest versions of chrome and firefox.
Tried using a requestAnimationFrame
Using rotate3d so the gpu takes over
Checked if scroll event fires ( it does. )
Tried same animation with opacity instead of changing top
Some code:
// The event listener is added
componentDidMount = () => {
window.addEventListener('scroll', throttle(this.handleScroll, 100));
window.addEventListener('keyup', this.handleKeyPress);
}
// What the event fires
handleScroll = () => {
const { currentNodeType } = this.props;
if (currentNodeType === 'landing_page') {
this.setState({ navScrolled: window.scrollY > (window.innerHeight - 125) });
} else {
this.setState({ navScrolled: window.scrollY > 285 });
}
const { prevScrollpos } = this.state;
const currentScrollPos = window.pageYOffset;
const visible = !!(prevScrollpos > currentScrollPos || window.scrollY < 30);
this.setState({
prevScrollpos: currentScrollPos,
visible,
});
}
A class is then added or removed to the navigation depending on if its visible
// The classes
.nav-hidden {
transition: all 0.333s ease-out;
top: -125px;
}
.nav-visible {
transition: all 0.333s ease-out;
top: 0;
}
I can't post images because of rep needed. ( this is my first question on stack overflow )
behaviour on chrome - desired behaviour
https://imgur.com/WkhlS4Y.gif
behaviour on firefox - undesired behaviour
https://imgur.com/hoEOP4x.gif
I hope I've been clear enough. Thanks in advance!
Fixed this by using margin instead of top.
.nav-hidden {
transition: all 0.333s ease-out;
top: 0;
margin-top: -125px;
}
.nav-visible {
transition: all 0.333s ease-out;
top: 0;
}
Hope this helps!
Related
I have some problems with rendering animation on my nextjs app with a tailwind. Namely, I have just a simple animation that makes the logo smaller on scroll, but it seems it lags on Android devices. I have tested from 2 different iPhones, a few laptops and three android phones (Samsung Note10, Flex 4 and some Xioami). Only on android happens that is lagging.
So what I'm doing is (first check scroll position) (it shouldn't be a problem)
useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
useEffect(() => {
offsetX < 1024 ? offsetY > 40 ? setScroll(true) : setScroll(false) : undefined
offsetX >= 1024 ? offsetY > 76 ? setScroll(true) : setScroll(false) : undefined
}, [offsetY, offsetX])
const stickNavbar = () => {
if (window !== undefined) {
setOffsetY(window.scrollY)
setOffsetX(screen.width)
}
};
and ofc, I'm checking when position is true to assign the class. I made my CSS class in order to test those performance issues:
${scroll ? 'w-8 scrolled' : ''}
So here is the final CSS:
.nav {
.logo{
//transition-delay: 0.15s;
transition-property: all;
transition-timing-function: ease-in-out;
transition-duration: 0.3s;
top: -1.5rem;
&.scrolled{
margin-top: 2.15rem;
margin-left: 0.75rem;
}
#include desktop(){
transition-delay: 0s;
}
}
}
As you see, I have commented delay; causing a delay could be a possible fix to avoid that much lag. But is there any other things that I should look for?
Here is the demo of the app enter link description here
On the website (please don't share), in WordPress, I set a sticky header using CSS
header#masthead {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 10000;
}
This works correctly. However, the image in the header is too big, that's why I resized it with an animation when scrolling down
jQuery(document).ready(function() {
jQuery(function() {
var $nav = jQuery('#masthead .custom-logo');
var height_original = jQuery('#masthead .custom-logo').css("height").replace("px","");
var height_small = height_original * 0.666;
var width_original = jQuery('#masthead .custom-logo').css("width").replace("px","");
var width_small = width_original * 0.666;
jQuery(document).scroll( function() {
var value = jQuery(this).scrollTop();
if ( value > 0 ){
$nav.stop().animate({height:height_small,width:width_small},100);
} else if (value == 0 ) {
$nav.stop().animate({height:height_original,width:width_original},100);
}
});
});
});
But, it doesn't work properly.
I primarily use Opera GX, where it behaves like this - when scrolling down, the animation is slowed down. Also, if you just scroll down a little, the animation doesn't run all the way and the image goes back to its original size, scrolling up works without a problem.
The strange thing is that I've also tried it in Firefox, Chrome and Edge. It behaves differently in everyone, but nowhere does it work 100% correctly.
What is wrong with the code please?
Thank you
I think instead of that long jquery code you can use this simple javascript code with some css to get the results you want:
I hope this helps you to reach what you looking for :)
JS
// Add a class to the header when scrolling
let header = document.querySelector('header');
window.addEventListener('scroll' , function () {
let window_top = this.scrollY;
if (window_top == 0) {
header.classList.remove('resize');
}else {
header.classList.add('resize');
}
});
CSS
/* these are the default styles (when the user doesnt scroll down yet) */
header#masthead {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 10000;
transition: .3s;
}
header#masthead img{
transition: .3s; /*here i added transition to give the image a smooth animation*/
}
/* these are the styles when the user scrolls */
header#masthead.resize img{
height: 50px; /* <=== here i gived the image a smaller size */
}
I'm trying to use the composition API to create a function that can hide the navbar when scrolling up and show it when scrolling down. I already have this function in vanilla JS, but in Vue3 I'm having difficulty bringing it into the DOM.
In vanilla JS:
let prevScrollpos = window.pageYOffset;
window.onscroll = function () {
console.log('scrolling')
let currentScrollPos = window.pageYOffset;
if (prevScrollpos > currentScrollPos) {
// #ts-ignore
document.getElementById("navbar").style.top = "0";
} else {
// #ts-ignore
document.getElementById("navbar").style.top = "-65px";
}
prevScrollpos = currentScrollPos;
}
My issue is, in Vue3, I can't seem to register some simple scroll function such as pageYOffset more than once. I am not having any success with the #scroll="function" directive, or with event listeners. At the moment, I'm attempting to use a directive to see the DOM at the base layer, but I don't know how to continually track the scroll position this way either. I would be happy for a fix using a directive but I would also be glad to have a solution that existed just within the Navbar.vue, the Homepage.vue, or the App.vue.
So how do I make Vue3 see the window's scroll position using the Composition API?
Alright, so I actually found an answer with the help of my instructor and this post. I didn't realize that because my parallax scrolling effect (which isn't shown above) had all the scrolling happening in the main tag, it meant that the window never saw any scrolling. Also, I didn't realize that putting this onscroll effect in an onMounted hook worked just fine to interact with the DOM in the Composition API, probably because of the first problem. I fixed it, and now my finished code looks like this.
onMounted(() => {
let main = document.querySelector("main");
let prevScrollpos = main.scrollTop;
let nav = document.querySelector(".navbar");
main.onscroll = function () {
let currentScrollPos = main.scrollTop;
if (prevScrollpos > currentScrollPos) {
// #ts-ignore
nav.classList.add("navbar_hidden");
} else {
// #ts-ignore
nav.classList.remove("navbar_hidden");
}
prevScrollpos = currentScrollPos;
};
});
Because its now in the main tag, I couldn't use PageYOffset so I changed that to scrollTop. Here's the CSS. All this is in the Navbar.vue component
.navbar {
background: $dark;
transform: translate3d(0, 0, 0);
transition: all 0.2s ease-out;
}
.navbar_hidden {
transform: translate3d(0, -100%, 0);
}
Thus, my not-so-thrilling saga comes to a close.
I'm writing a code which will hide my navbar on scroll and display it again accordingly.
So far it works well, but I was curious on how implement some animation or transition to reveal/hide that element.
Here's my code so far
var prevScrollpos = window.pageYOffset;
window.onscroll = function() {
var currentScrollPos = window.pageYOffset;
if (prevScrollpos > currentScrollPos) {
document.getElementById("bottom-navigation").style.bottom = "0";
} else {
document.getElementById("bottom-navigation").style.bottom = "-100px";
}
prevScrollpos = currentScrollPos;
}
Use CSS transform and transition which (in contrast to other properties like bottom top etc) can be GPU accelerated
Use a special class like i.e: .hidden or .hide
than, all you need in JavaScript is a Element.classList.toggle(className, boolean) to toggle that class
Never use on(eventname)- unless you're creating brand new elements from in-memory.
on* will override any previously added eventname to that element. Use a cumulative approach instead, by using Element.addEventListener()
const EL_bottomNav = document.querySelector("#bottom-navigation");
let prevScrollpos = window.pageYOffset;
const toggleBottomNav = () => {
EL_bottomNav.classList.toggle("hide", prevScrollpos <= window.pageYOffset);
prevScrollpos = window.pageYOffset;
};
// Do immediately:
// toggleBottomNav();
// And on page scroll:
window.addEventListener("scroll", toggleBottomNav);
/* QuickReset */ * {margin:0; box-sizing: border-box; }
body {
min-height: 300vh; /* just to force some scrollbars */
}
#bottom-navigation {
position: fixed;
width: 100%;
bottom:0;
background: gold;
padding: 30px;
transition: 0.5s;
}
#bottom-navigation.hide {
transform: translateY(100%);
}
Scroll down and than up
<footer id="bottom-navigation">I'm the bottom nav!</footer>
Nota bene:
Since Events added on scroll are expensive, don't query the DOM for your Element. Cache it beforehand instead (like in the example above). Also a throttle function would be helpful to leverage the load of function calls passed to the event loop in main thread.
Problem statement
To move the square along the perimeter of the viewport on click of the button as can be seen in the example:
https://codepen.io/vineetrok/pen/XRowdB
What do I need?
I'm using this code in combination with the transition property in the CSS. I think combination of transition and setInterval() is causing a delay. Is there a better and efficient method to accomplish this only using javascript?
Following is my code:
HTML
<div class="box" style="left:0;top:0"></div>
<button type="button" name="button" onclick="init()">Start!</button>
CSS
.box{
transition: all 1s linear;
}
JS
var elem = document.querySelector(".box");
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var dimension = elem.clientWidth;
var deltaX = viewportWidth - dimension;
var deltaY = viewportHeight - dimension;
function move(x,y){
if(x <=0 && y==0){
elem.style.left=(deltaX)+"px";
}
else if(x==(deltaX) && y==0){
elem.style.top=(deltaY)+"px";
}
else if(x==(deltaX) && y==(deltaY)){
elem.style.left="0px";
}
else if(x==0 && y==(deltaY)){
elem.style.top="0px";
}
}
function getCoordinates(elem){
return {
x: elem.getBoundingClientRect().left,
y: elem.getBoundingClientRect().top
}
}
var init = function(){
var clearTimer = 1;
var startTimer = setInterval(function(){
move(getCoordinates(elem).x,getCoordinates(elem).y )
}, 1000);
clearTimer++;
if(clearTimer>=4){
clearInterval(startTimer);
}
}
I would generally say that using both css and javascript to manage a transition is going to cause trouble. Part of the problem is that javascript timers aren't very precise. If you set a timer for 1 second it doesn't actually sleep for exactly one second. The exact amount of time it sleeps can vary depending on how busy the CPU is, what the user is doing, etc. It is very easy for the javascript timer to take longer than the CSS animation.
Since you are using jQuery I would use the jQuery.animate function to run things. It has a callback function that is invoked when the animation completes, and you can use that to execute the next step of the animation without any timers at all. That will make sure there aren't any delays. It should also be fairly performant. CSS animations are usually the slowest in terms of computer performance, so I expect jQuery.anmiate to probably be a bit better. There are other libraries out there designed for high performance animations, but unless performance actually becomes a problem, I wouldn't worry about it. Right now your issue is likely the imprecise timing of the timeout method, and not any performance issues.
Here's my go at it (I developed something from scratch instead of reusing your code) :
let box=document.getElementById("box"),
isLeft = false,
isTop = false
const toggleLeft = () => {
box.style.left = (isLeft=!isLeft) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleTop, 2000);
}
const toggleTop = () => {
box.style.top = (isTop=!isTop) ? "calc( 100% - 50px )" : "0";
setTimeout(toggleLeft, 2000);
}
setTimeout(toggleLeft, 1000)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>
And a more condensed and recursive version :
let box=document.getElementById("box"),
is = { left : false, top : false }
const toggle = what => {
box.style[what] = (is[what]=!is[what]) ? "calc( 100% - 50px )" : "0";
setTimeout(()=>toggle(what==="left"?"top":"left"), 2000);
}
setTimeout(()=>toggle("left"), 100)
#box {
width: 50px;
height: 50px;
background: #00f;
position: absolute;
top: 0;
left: 0;
-webkit-transition: all 2s ease-in-out;
transition: all 2s ease-in-out;
}
<div id="box"></div>