How to properly make a transition between relative and fixed HTML element? - javascript

On my simple website I have a horizontal navigation bar with several button there. When I scroll down I want it to be pinned on top.
For that I use a JS script which changes display type of the navigation bar from position:relative to position:fixed at a certain moment.
But the problem occurs with the website contents following the nav bar. Since elements with position:fixed are removed from the DOM flow and there is no created space for them, following contents displace from their position consequently being overflowed by the nav bar.
My navigation bar might change its shape due to wrap. It might also be moved down the page since upper horizontal elements may wrap too.
What the right way to do it? Is there any way to create space in the DOM for elements with position:fixed?
Main CSS:
main {
padding-top: 0;
margin-left: 20px;
margin-top: 0;
}
Navigation bar CSS:
.layout-nav {
display: flex;
flex-flow: wrap;
position: relative;
top: 0;
width: 100%;
padding: 10px 0 0;
margin-left: 10px;
background: #424242;
}
The JS script with a method which changes display type and padding on a certain scrollY which vary depending on the window width which leads to certain wrap:
let lastScrollY = 0;
let lastWidth = window.innerWidth;
let maxScrollPos = 105;
let ticking = false;
let fixed = false;
function modifyTitle(scrollPos, width) {
if (width > 773) {
maxScrollPos = 105;
} else {
maxScrollPos = 164;
}
if (scrollPos > maxScrollPos && !fixed) {
const topNav = document.querySelector('.layout-nav');
const main = document.querySelector('main');
topNav.style.position = "fixed";
topNav.style.padding = "23px 0 0";
main.style.paddingTop = "70px";
fixed = true;
} else if (scrollPos <= maxScrollPos && fixed) {
const topNav = document.querySelector('.layout-nav');
const main = document.querySelector('main');
topNav.style.position = "relative";
topNav.style.padding = "10px 0 0";
main.style.paddingTop = "0";
fixed = false;
}
}
document.addEventListener('scroll', (e) => {
lastScrollY = window.scrollY;
lastWidth = window.innerWidth;
if (!ticking) {
window.requestAnimationFrame(() => {
modifyTitle(lastScrollY, lastWidth);
ticking = false;
});
ticking = true;
}
});
So as of now I hardcoded some cases of wraps so that after navigation bar being pinned following content would be shown properly.
It is hard to hardcode every case.
And this solution seems not right at all for me since it is based not just on scrollY of the page but also on the width of the windows which indirectly causes certain wraps based on the resolution.

Related

Zoom on Scroll Down & Scroll up the zoomed element when scale is more than full screen

I tried to make zoom in Zoom out on scroll. The element is zooming on scrolling but I want to make it(the zoomed element) go up once it reached the full width of the screen.
Here's my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.zoom{
height:100vh;
width:100%;
display:grid;
place-items:center;
position:fixed;
top:0;
left:0;
background: url('img/bg.png');
}
.afterzoom{
height: 200vh;
background: red;
}
</style>
</head>
<body>
<div class="zoom">
<!-- <h1>Zoom meeeee</h1> -->
<img src="img/square.png" alt="">
</div>
<div class="afterzoom"></div>
<script>
const zoomElement = document.querySelector(".zoom");
let zoom = 1;
const ZOOM_SPEED = 0.1;
document.addEventListener("wheel", function(e) {
if(e.deltaY > 0){
if (zoomElement.style.transform >= `scale(5)`) {
console.log("now scroll down");
return false;
}
zoomElement.style.transform = `scale(${zoom += ZOOM_SPEED})`;
}else{
if (zoomElement.style.transform == `scale(1)`) {
// console.log("minus");
return false;
}
zoomElement.style.transform = `scale(${zoom -= ZOOM_SPEED})`; }
});
</script>
</body>
</html>
Fiddle: https://jsfiddle.net/mayureshpitale/6etpn0vs/3/
I am trying to make something like this: http://truegossiper.com/vilson17/
A bit of a different approach using the scroll event and window.scrollY for positioning together with requestAnimationFrame (RAF).
This also works, if the image (or other content) is not squared.
The problem with the wheel event is, that it does not trigger when the user uses the scrollbar or arrow keys to scroll the page.
The problem with the scroll event is, that it executes a lot... that's why RAF is used, to only execute necessary zoom changes.
Keep in mind, this exact code works with the document width when the page is loaded. If the user resizes the window or changes the browsers zoom, the element will resize to the same scale as before. You could fix this with some extra code by using a ResizeObserver. (Remove the existing event listener, resize the Image and execute parts of the code again, every time the document is resized...)
You will notice this, when you try to view the below snippet as full page.
const zoomElement = document.querySelector('.zoom')
const fadeElement = document.querySelector('.fade')
const afterZoomElement = document.querySelector('.afterzoom')
const imgElement = document.querySelector('img')
const WIDTH = document.body.clientWidth
const HEIGHT = zoomElement.clientHeight
const IMAGE_WIDTH = imgElement.clientWidth
const IMAGE_HEIGHT = imgElement.clientHeight
const ZOOM_SPEED = 100 // Lower is faster
const ZOOM_BREAKPOINT = WIDTH / IMAGE_WIDTH // When it should stop zooming in
const IMAGE_HEIGHT_MAX = IMAGE_HEIGHT * ZOOM_BREAKPOINT
const ABSOLUTE = ZOOM_BREAKPOINT * ZOOM_SPEED // Absolute position, when the Element reached maximum size
// Fade --------------------------------------------------------------------------------------
const FADE_SPEED = 500 // Lower is faster
let fade = 1
let prev = 0
// -------------------------------------------------------------------------------------- Fade
function anim() {
let scroll = window.scrollY
let temp = scroll / ZOOM_SPEED
let zoom = temp > 1 ? temp : 1
// Only update the Elements scale, when we are below the breakpoint
if (zoom < ZOOM_BREAKPOINT) {
// Only scale the Image, so the Zoom element does not mess with the document width
imgElement.style.transform = `scale(${zoom})`
// Sets the Elements position to fixed, so it can resize without scrolling away
zoomElement.style.top = '0px'
zoomElement.style.position = 'fixed'
} else {
// Makes sure the Element always reaches Max Size
imgElement.style.transform = `scale(${ZOOM_BREAKPOINT})`
// Sets the elements position to absolute, so it will scroll with the rest of the document
zoomElement.style.position = 'absolute'
zoomElement.style.top = ABSOLUTE + 'px'
}
// Fade --------------------------------------------------------------------------------------
let dif = prev - scroll
if (zoom < ZOOM_BREAKPOINT - FADE_SPEED / ZOOM_SPEED) {
fade = 1
} else if (zoom > ZOOM_BREAKPOINT) {
fade = 0
} else {
fade += dif / FADE_SPEED
}
fadeElement.style.opacity = fade
prev = scroll
// -------------------------------------------------------------------------------------- Fade
}
// Resets scroll position on every reload
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual'
}
document.addEventListener('scroll', () => window.requestAnimationFrame(anim))
// Fade --------------------------------------------------------------------------------------
zoomElement.style.opacity = 1
// -------------------------------------------------------------------------------------- Fade
// Positions the afterZoom element right below the zoomed image
afterZoomElement.style.top = ABSOLUTE + IMAGE_HEIGHT_MAX / 2 + HEIGHT / 2 + 'px'
body {
margin: 0;
}
img {
width: 150px;
height: 150px;
background-color: black;
}
.fade {
height: 100vh;
width: 100%;
position: fixed;
top: 0;
left: 0;
background: blue;
}
.zoom {
height: 100vh;
width: 100%;
display: grid;
place-items: center;
position: fixed;
top: 0;
left: 0;
}
.afterzoom {
position: absolute;
height: 200vh;
width: 100%;
background: red;
overflow-x: auto;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div class="fade"></div>
<div class="zoom">
<img src="https://via.placeholder.com/150" alt="">
</div>
<div class="afterzoom">
<p>This should appear after the above element is fully zoomed.</p>
</div>
</body>
I got a bit carried away during coding and added some fading-in and -out to the blue background. Not necessary, but looks nice. You can remove the Fade ----- Fade parts of the code, without affecting functionality.
To alter zoom and fade speed, simply change the ZOOM_SPEED and FADE_SPEED variables.

Weird scroll behavior when with chrome and smooth scrolling

I'm writing a virtual scrolling lib and encounter a weird problem which only appears in chrome and disable smooth scrolling in chrome flag fix it. One part of the logic is to use an invisible div to stretch the container and when scroll to top, fetch some new data then scroll back to old top. However, in chrome with smooth scrolling, it seems that changes to scrollTop will not be applied immediately so multiple data is fetched and scroll position is wrong.
I'm using chrome 84.0.4147.105-1 on Linux and have tested on Windows with same result. It may sometimes be hard to trigger, the safest way is to drag the scrollbar near top without trigger fetching and scroll up. Then you shall find console value jump from 0 to 900(scroll down) to a small number(why?) and trigger fetch again
const container = document.getElementById('container')
const scroller = document.getElementById('scroller')
let height = 0
let isFetching = false
const fetchData = h => {
if (isFetching) return
isFetching = true
setTimeout(() => {
height += h
scroller.style.height = height + 'px'
container.scrollTop += h
isFetching = false
}, 50)
}
fetchData(800)
container.addEventListener('scroll', ev => {
const top = ev.target.scrollTop
console.log(top)
if (top <= 50) {
fetchData(800)
}
})
#container {
height: 200px;
width: 500px;
background-color: blue;
overflow: auto;
}
#scroller {
width: 100%;
}
<div id="container">
<div id="scroller"></div>
</div>
A temporary (and really inelegant) fix is changing fetchData to
const fetchData = h => {
if (isFetching) return
isFetching = true
setTimeout(() => {
height += h
scroller.style.height = height + 'px'
container.scrollTop += h
isFetching = false
setTimeout(() => container.scrollTop += h, 200)
}, 50)
}
which scrolls again after a small delay

2 divs with fixed images that respond to scrolling

I am trying to create a scrolling animation with 2 divs and 2 images.
For lack of a better explanation (as you might have guessed from the title) I have made a quick animation showcasing what I am trying to achieve.
here is a hosted version that I made earlier. I tried to create the effect with the help of parallax scrolling, but it's not quite what I want.
It's a Zeit Now deployment, so you can append /_src to the url and take a look at the source code.
Now I am not sure if this is even the correct way to create the animation and to be honest I wouldn't know any other way that I could approach this.
So I am not asking for a fully-fledged answer without any flaws (although it would be much appreciated), but rather a nudge in the right direction.
Made this quickly so there might be some issues, I tried to make the variables somehow general so you can play with things (check this fiddle)
const body = document.body,
html = document.documentElement;
const targetImg = document.querySelector('.second');
// our image's initial height
const imgHeight = targetImg.clientHeight;
// the final value for image height (at scroll end)
const imgTargetHeight = 0;
// total height of our document
const totalHeight = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
// visible window height
const windowHeight = window.innerHeight;
// starting scroll position we want to start calculations from (at this point and before, our image's height should equal its initial height 'imgHeight')
const fromScroll = 0;
// final scroll position (at this point and after, our image's height should equal 'imgTargetHeight')
const toScroll = totalHeight - windowHeight;
window.addEventListener('scroll', function() {
// get current scroll position, these multiple ORs are just to account for browser inconsistencies.
let scrollPos = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop;
// force the scroll position value used in our calculation to be between 'fromScroll` and 'toScroll'
// In this example this won't have any
// effect since fromScroll is 0 and toScroll is the final possible scroll position 'totalHeight - windowHeight',
// but they don't have to be, try setting fromScroll = 100 and toScroll = totalHeight - windowHeight - 100 for example to see the difference.
// the next line is just a shorthand for:
// if (scrollPos <= fromScroll) {
// scrollPos = fromScroll;
// } else if (scrollPos >= toScroll) {
// scrollPos = toScroll;
// } else {
// scrollPos = scrollPos;
// }
scrollPos = scrollPos <= fromScroll ? fromScroll : (scrollPos >= toScroll ? toScroll : scrollPos);
// our main calculation, how much should we add to the initial image height at our current scroll position.
const value = (imgTargetHeight - imgHeight) * (scrollPos - fromScroll) / (toScroll - fromScroll);
targetImg.style.height = imgHeight + value + "px";
});
.container {
height: 200vh;
}
.img-container {
position: fixed;
width: 100vw;
height: 100vh;
text-align: center;
background: white;
overflow: hidden;
}
.second {
background: tomato;
}
img {
position: absolute;
left: 50vw;
top: 50vh;
transform: translate(-50%, -50%);
}
<div class="container">
<div class="img-container first">
<img src="https://fixedscrollingtest-takidbrplw.now.sh/luigi.png" alt="">
</div>
<div class="img-container second">
<img src="https://fixedscrollingtest-takidbrplw.now.sh/mario.png" alt="">
</div>
</div>

Keep left column visible AND scrollable

It's easy to keep a column in my layout fixed so it's always visible, even when the user scrolls down.
It's also easy to only move the column down the page when the page is scrolled down far enough for it to be out of the viewport so it's anchored before scrolling starts.
My problem is, I have left hand column that is taller than the average window so you need to be able to scroll down to see all the content (controls) in the left column but at the same time when you scroll up you want to see the top of the controls again.
Here's a visual of what I want to accomplish:
So the left column is always occupying 100% of the height of the window but as the user scrolls down they can see the bottom of the div, and when they start to scroll up the scrolls up until it reaches the top of the window again. So no matter how far they scroll the page, the top of the div is always nearby.
Is there some jQuery magic to make this happen?
Did you mean something like this? (Demo)
var sidebar = document.getElementById('sidebar');
var sidebarScroll = 0;
var lastScroll = 0;
var topMargin = sidebar.offsetTop;
sidebar.style.bottom = 'auto';
function update() {
var delta = window.scrollY - lastScroll;
sidebarScroll += delta;
lastScroll = window.scrollY;
if(sidebarScroll < 0) {
sidebarScroll = 0;
} else if(sidebarScroll > sidebar.scrollHeight - window.innerHeight + topMargin * 2) {
sidebarScroll = sidebar.scrollHeight - window.innerHeight + topMargin * 2;
}
sidebar.style.marginTop = -sidebarScroll + 'px';
}
document.addEventListener('scroll', update);
window.addEventListener('resize', update);
#sidebar {
background-color: #003;
bottom: 1em;
color: white;
left: 1%;
overflow: auto;
padding: 1em;
position: fixed;
right: 80%;
top: 1em;
}
body {
line-height: 1.6;
margin: 1em;
margin-left: 21%;
}
It almost degrades gracefully, too…
I made a fiddle for you, hope this helps you out abit.
I detect scroll up or scroll down, and set the fixed position accordion to the direction.
http://jsfiddle.net/8eruY/
CSS
aside {
position:fixed;
height:140%;
background-color:red;
width:100px;
top:20px;
left:20px;
}
Javascript
//Detect user scroll down or scroll up in jQuery
var mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel" //FF doesn't recognize mousewheel as of FF3.x
$('html').bind(mousewheelevt, function(e){
var evt = window.event || e //equalize event object
evt = evt.originalEvent ? evt.originalEvent : evt; //convert to originalEvent if possible
var delta = evt.detail ? evt.detail*(-40) : evt.wheelDelta //check for detail first, because it is used by Opera and FF
if(delta > 0) {
$('aside').css('top', '20px');
$('aside').css('bottom', 'auto');
}
else{
$('aside').css('bottom', '20px');
$('aside').css('top', 'auto');
}
});
http://jsfiddle.net/KCrFe/
or this:
.top-aligned {
position: fixed;
top: 10px;
}
with
var scrollPos
$(window).scroll(function(event){
var pos = $(this).scrollTop();
if ( pos < scrollPos){
$('.sidebar').addClass('top-aligned');
} else {
$('.sidebar').removeClass('top-aligned');
}
scrollPos = pos;
});

y position issue for background images via css and javascript

I have implemented a parallax scrolling effect based on a tutorial I found. The effect works great. However, when I specify the background images, I am unable to control the y (vertical) axis. This is causing problems because I'm trying to set locations on multiple layered images.
Any thoughts on what's causing the problem?
Here is one external script:
$(document).ready(function(){
$('#nav').localScroll(800);
//.parallax(xPosition, speedFactor, outerHeight) options:
//xPosition - Horizontal position of the element
//inertia - speed to move relative to vertical scroll. Example: 0.1 is one tenth the speed of scrolling, 2 is twice the speed of scrolling
//outerHeight (true/false) - Whether or not jQuery should use it's outerHeight option to determine when a section is in the viewport
$('#mainimagewrapper').parallax("50%", 1.3);
$('#secondaryimagewrapper').parallax("50%", 0.5);
$('.image2').parallax("50%", -0.1);
$('#aboutwrapper').parallax("50%", 1.7);
$('.image4').parallax("50%", 1.5);
})
This is another external script:
(function( $ ){
var $window = $(window);
var windowHeight = $window.height();
$window.resize(function () {
windowHeight = $window.height();
});
$.fn.parallax = function(xpos, speedFactor, outerHeight) {
var $this = $(this);
var getHeight;
var firstTop;
var paddingTop = 0;
//get the starting position of each element to have parallax applied to it
$this.each(function(){
firstTop = $this.offset().top;
});
if (outerHeight) {
getHeight = function(jqo) {
return jqo.outerHeight(true);
};
} else {
getHeight = function(jqo) {
return jqo.height();
};
}
// setup defaults if arguments aren't specified
if (arguments.length < 1 || xpos === null) xpos = "50%";
if (arguments.length < 2 || speedFactor === null) speedFactor = 0.1;
if (arguments.length < 3 || outerHeight === null) outerHeight = true;
// function to be called whenever the window is scrolled or resized
function update(){
var pos = $window.scrollTop();
$this.each(function(){
var $element = $(this);
var top = $element.offset().top;
var height = getHeight($element);
// Check if totally above or totally below viewport
if (top + height < pos || top > pos + windowHeight) {
return;
}
$this.css('backgroundPosition', xpos + " " + Math.round((firstTop - pos) * speedFactor) + "px");
});
}
$window.bind('scroll', update).resize(update);
update();
};
})(jQuery);
Here is the CSS for one section:
#aboutwrapper {
background-image: url(../images/polaroid.png);
background-position: 50% 0;
background-repeat: no-repeat;
background-attachment: fixed;
color: white;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
#aboutwrapper .image4 {
background: url(../images/polaroid2.png) 50% 0 no-repeat fixed;
height: 500px;
width: 100%;
margin: 0 auto;
padding: 0;
}
.image3{
margin: 0 auto;
min-width: 970px;
overflow: auto;
width: 970px;
}
Both of these are being called to achieve the parallax scrolling. I really just want to more specifically control the background image locations. I've tried messing with the CSS background position and I've messed with the first javascript snippet as well. No luck.
just a quick shot, have you tried actually placing the images, either in a div or just using the img src tag to actually move the element rather than manipulating the y axis of a background image?

Categories

Resources