Div “flickers” on page/window scroll in Safari browser - javascript

Trying to fix div "flicker" on page scroll in Safari browser ( ver 12.0.2 on macOS High Sierra), but don't understand why this is happening, also i've tried to fix this via CSS methods, like transformZ(0), webkit-backface-visibility: hidden, webkit-transform-style: preserve-3d, will-change: top, but this doesn't help, any ideas? thanks!
var scroll = document.getElementById('scroll').style;
window.onscroll = function () {
scroll.top = middleOfScreen();
};
var raf = document.getElementById('raf').style;
requestAnimationFrame(function setRaf () {
raf.top = middleOfScreen();
requestAnimationFrame(setRaf);
});
function middleOfScreen() {
return window.pageYOffset + window.innerHeight/2 + 'px';
}
body {
height: 20000px;
margin: 0;
font-family: sans-serif;
text-align: center;
}
div {
width: 23%;
top: 50%;
background-color: rgba(0, 0, 0, 0.2);
padding: 1em 0;
}
div:nth-child(2n) {
background-color: rgba(0, 0, 0, 0.4);
}
#static {
position: absolute;
left: 0;
}
#scroll {
position: absolute;
left: 24%;
}
#raf {
position: absolute;
left: 48%;
}
#css {
position: fixed;
right: 0;
}
<div id=static>position:static</div>
<div id=scroll>set to top:50% onScroll</div>
<div id=raf>set to top:50% on requestAnimationFrame</div>
<div id=css>posision:fixed </div>
also Codepen

Why do you need to set top every time your scroll event fire? it seems that setting top value affecting to the flicker. If you want to listen on scroll and set top anyway so I suggest to use throttle so your event listeners will do only once.

Related

Can I use requestAnimationFrame to smooth out scroll behaviour?

I have a small scroll effect which simulate that a logo will disappear if a lower div will scroll over it.
Currently I'm checking if two divs are intersecting. If this is true, then the height of the div of the logo will decrease with the scroll position of the div beneath.
Unfortunately, my demo is not foolproof and some fragments of the logo are still visible.
Is there a way to do this jank-free? Maybe with requestAnimationFrame?
function elementsOverlap(el1, el2) {
const domRect1 = el1.getBoundingClientRect();
const domRect2 = el2.getBoundingClientRect();
return !(
domRect1.top > domRect2.bottom ||
domRect1.right < domRect2.left ||
domRect1.bottom < domRect2.top ||
domRect1.left > domRect2.right
);
}
const el1 = document.querySelector(".logo");
const el2 = document.querySelector(".clickblocks");
let scrollPositionEl2;
let heightDifference;
const logoHeight = el1.offsetHeight;
document.addEventListener("DOMContentLoaded", () => {
var scrollDirectionDown;
scrollDirectionDown = true;
window.addEventListener("scroll", () => {
if (this.oldScroll > this.scrollY) {
scrollDirectionDown = false;
} else {
scrollDirectionDown = true;
}
this.oldScroll = this.scrollY;
// test
if (scrollDirectionDown) {
if (elementsOverlap(el1, el2) === true) {
scrollPositionEl2 = el2.getBoundingClientRect().top;
heightDifference = logoHeight - scrollPositionEl2 + 100;
//console.log(logoHeight - heightDifference);
el1.style.height = `${logoHeight - heightDifference}px`;
}
} else {
//scrolling up
scrollPositionEl2 = el2.getBoundingClientRect().top - 100;
el1.style.height = `${scrollPositionEl2}px`;
//console.log(logoHeight);
}
});
});
#import url("https://fonts.googleapis.com/css2?family=Inter:wght#900&display=swap");
.wrapper {
max-width: 100vw;
margin: 0 auto;
background-image: url("https://picsum.photos/1920/1080");
background-size: cover;
background-attachment: fixed;
height: 1200px;
position: relative;
&::after {
content: "";
position: absolute;
background: rgba(0, 0, 0, 0.3);
width: 100%;
height: 100%;
inset: 0;
}
}
body {
margin: 0;
}
main {
width: 100%;
height: 100vh;
position: relative;
z-index: 1;
}
.clickblocks {
width: 100%;
height: 200px;
display: grid;
grid-template-columns: repeat(12, (minmax(0, 1fr)));
}
.clickblock {
transition: all ease-in-out 0.2s;
backdrop-filter: blur(0px);
border: 1px solid #fff;
height: 100%;
grid-column: span 6 / span 6;
font-size: 54px;
font-weight: 700;
padding: 24px;
font-family: "Inter", sans-serif;
color: white;
text-transform: uppercase;
&:hover {
backdrop-filter: blur(10px);
}
}
.logo {
background: url("https://svgshare.com/i/ivR.svg");
width: 100%;
background-repeat: no-repeat;
background-position: top;
position: fixed;
top: 100px;
}
.logo-wrapper {
position: relative;
}
<div class="wrapper">
<main>
<div class="logo-wrapper" style="height: 390px">
<div class="logo" style="height: 300px">
</div>
</div>
<div class="clickblocks">
<div class="clickblock">
Some Content
</div>
</div>
</main>
</div>
Few things here to optimize your performance.
getBoundingClientRect() is a rather expensive calculation. If there are NO other options it's fine.
The Intersection Observer API is a lot more performant, and you can set the root element on the API. Then observe the element that is moving. This should be able to telly you if their are colliding.
Whenever you do scroll based logic, you should really try and throttle the logic so that the scroll any fires ever 16.6ms. That will reduce the number of times the calculations are made, and speed things up on the FE.
Learn how to use Google Chrome's performance tab. It can be overwhelming at first, but it gives you the ability to drill into the exact piece of code that's slowing your site down.
Learn about JS's event loop, and what's really going on under the hood. This video by Jake Archibald really help me understand it.
Hope this helped, sorry that I didn't give you an actual solution.

Click event does not work with custom cursor

I am trying to create a custom cursor on a website (a blurry yellow spot). I created a div in HTML for the custom cursor and styled it in CSS. I gave the 'cursor: none' property to the body tag to hide the default cursor. I also put 'pointer-events: none' on the custom cursor div. Still, click events are not (or hardly) working on buttons (for example I cannot close a pop-up window with the close button). When I remove 'cursor: none', everything works fine, but the default cursor returns beside the yellow spot. Could you please help me in solving this? How could I remove the default cursor without affecting click events? Thank you in advance.
// move yellow spot as cursor
const moveCursor = (e) => {
const mouseY = e.clientY;
const mouseX = e.clientX;
const yellowSpot = document.querySelector(".yellow-spot");
yellowSpot.style.transform = `translate3d(${mouseX}px, ${mouseY}px, 0)`;
}
window.addEventListener('mousemove', moveCursor);
document.querySelector("input[type=button]").addEventListener("click", () => {
console.log("Button clicked");
});
*,
body {
cursor: none !important;
}
.yellow-spot {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 1.625rem;
height: 1.625rem;
border-radius: 50%;
background: #ffeb77;
box-shadow: 0 0 15px 5px #ffeb77;
pointer-events: none;
}
<div class="yellow-spot"></div>
<input type="button" value="Click Me">
The issue is that the actual cursor is at the top-left of the yellow spot, not in the middle, so it's easy to miss things when trying to click on them. You can see that if you remove the cursor: none rule:
// move yellow spot as cursor
const moveCursor = (e) => {
const mouseY = e.clientY;
const mouseX = e.clientX;
const yellowSpot = document.querySelector(".yellow-spot");
yellowSpot.style.transform = `translate3d(${mouseX}px, ${mouseY}px, 0)`;
}
window.addEventListener('mousemove', moveCursor);
document.querySelector("input[type=button]").addEventListener("click", () => {
console.log("Button clicked");
});
*,
body {
/* cursor: none !important; */
}
.yellow-spot {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 1.625rem;
height: 1.625rem;
border-radius: 50%;
background: #ffeb77;
box-shadow: 0 0 15px 5px #ffeb77;
pointer-events: none;
}
<div class="yellow-spot"></div>
<input type="button" value="Click Me">
To fix it, center the yellow spot over the cursor rather than moving it to the top-left (I also changed how the yellow spot is moved, but that's not the important thing):
const yellowSpot = document.querySelector('.yellow-spot');
// move the yellow spot to the mouse position
document.addEventListener('mousemove', function(e) {
// Make sure the *center* of the yellow spot is where the
// cursor is, not the top left
const {clientWidth, clientHeight} = yellowSpot;
yellowSpot.style.left = ((e.pageX - (clientWidth / 2)) + 'px');
yellowSpot.style.top = (e.pageY - (clientHeight / 2)) + 'px';
});
*,
body {
cursor: none !important;
}
.yellow-spot {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 1.625rem;
height: 1.625rem;
border-radius: 50%;
background: #ffeb77;
box-shadow: 0 0 15px 5px #ffeb77;
pointer-events: none;
}
<div class="yellow-spot"></div>
<button onclick="alert('test')">Click me</button>
Here's a version with the cursor showing so you can see how it's centered in the yellow spot now:
const yellowSpot = document.querySelector('.yellow-spot');
// move the yellow spot to the mouse position
document.addEventListener('mousemove', function(e) {
// Make sure the *center* of the yellow spot is where the
// cursor is, not the top left
const {clientWidth, clientHeight} = yellowSpot;
yellowSpot.style.left = ((e.pageX - (clientWidth / 2)) + 'px');
yellowSpot.style.top = (e.pageY - (clientHeight / 2)) + 'px';
});
*,
body {
/*cursor: none !important;*/
}
.yellow-spot {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 1.625rem;
height: 1.625rem;
border-radius: 50%;
background: #ffeb77;
box-shadow: 0 0 15px 5px #ffeb77;
pointer-events: none;
}
<div class="yellow-spot"></div>
<button onclick="alert('test')">Click me</button>

Problems with moving a div by cursor if I move it too fast

to specify my question I wrote an standalone example of my problem. I want to precisely move a div inside a wrapping container (only in x-direction), like a trackbar. The wrapping div should specify the space for the slider.
My script works, if I slowly move the cursor. But if I move the cursor too fast I kind of loose the slider div somewhere inside the container. Especially in the right and left corner.
How can I improve the code to have a stable solution, without the need of librarys? I know that there is a kind of simple solution with jQuery, but I would be very happy if we could find a way in plain javascript.
var x_mouse_position;
var x_offset;
var isDown = false;
var new_slider_left_position;
var container = document.getElementById("container");
var slider = document.getElementById("slider");
slider.addEventListener('mousedown', function (e) {
isDown = true;
x_offset = slider.offsetLeft - e.clientX;
}, true);
document.addEventListener('mouseup', function () {
isDown = false;
}, true);
document.addEventListener('mousemove', function (event) {
if (isDown) {
x_mouse_position = event.clientX;
new_slider_left_position = x_mouse_position + x_offset;
if (new_slider_left_position >= 0 && new_slider_left_position <= container.offsetWidth - slider.offsetWidth) {
slider.style.left = new_slider_left_position + 'px';
}
}
}, true);
html,
body {
height: 100%;
width: 100%;
}
body {
background-color: antiquewhite;
display: flex;
justify-content: center;
align-items: center;
}
#container {
position: relative;
width: 400px;
height: 30px;
background-color: cornflowerblue;
border-radius: 5px;
overflow: hidden;
}
#slider {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
border: 1px solid black;
width: 50px;
height: 100%;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.2);
cursor: move;
}
<div id="container">
<div id="slider"></div>
</div>

Slight background zoom on DOM load?

How to reproduce the effect on this website :
https://shop.stripe.com/
I mean waiting for the DOM to fully load before showing anything, and then having the background image zooming out for 1s. Pretty cool.
It's done using different transition and transforms together. Demo: http://jsfiddle.net/lotusgodkk/eHAuh/2/
Key is to add/remove classes in document.ready
HTML:
<div id="DIV_1" class="scaled"></div>
JS:
$(document).ready(function () {
$('#DIV_1').attr('class', 'animatable');
setTimeout(function () {
$('#DIV_1').removeClass('animatable');
}, 1000)
});
CSS:
#DIV_1 {
background-position: 50% 50%;
bottom: 0px;
height: 472px;
left: 0px;
position: absolute;
right: 0px;
top: 0px;
width: 600px;
z-index: 1;
background: rgba(0, 0, 0, 0) url(https://shop.stripe.com/assets/images/showcase/thairu-kat.jpg) no-repeat scroll 50% 50% / cover padding-box border-box;
font: normal normal normal 16px/normal Helvetica, Arial, sans-serif;
zoom:1.1;
background-size:cover;
}
/*#DIV_1*/
.animatable {
-webkit-transition:all 750ms ease-out;
transition:all 750ms ease-out;
}
.scaled {
-webkit-transform:scale(1.02);
transform:scale(1.02);
}
You can also do it easily with pure javascript:
css:
#blackdiv { background: black; color: white; position: fixed; width: 100%; height: 100%; }
html:
<div id="blackdiv"></div>
<div>page content</div>
js:
window.onload = function(){
var blackdiv = document.getElementById('blackdiv');
blackdiv.style.opacity = 1;
doIt();
};
var doIt = function(){
if( blackdiv.style.opacity > 0 ){
console.log(blackdiv.style.opacity);
blackdiv.style.opacity -= .1;
setTimeout("doIt()", 100);
}
}
Check jsFiddle

Content that scrolls with page, but is inside a fixed positioned sidebar (with shadow effect)

I'm trying to develop following functionality for a sidebar. In a nutshell, Sidebar will have 100% height and will be absolutely positioned. Inside it there is content, which should scroll with the page, while sidebar is fixed. And as addition there is a shadow effect / response to show user if he can scroll down or up. So for example if there is something that can be scrolled down / up show shadow there, if not don't show shadow. I made a quick mockup, hopefully it will help you understand what happens if page is scrolled:
I made a quick jsfidle with content and sidebar, this is as far as I can get at the moment. http://jsfiddle.net/cJGVJ/3/
I assume this can't be achieved only with css and html and work cross browser, so jQuery solutions are welcome.
HTML
<div id="main"> <!-- Demo Content (Scroll down for sidebar) -->
<!-- Demo content here -->
</div>
<aside id="sidebar">
<div id="side-content-1"></div>
<div id="side-content-2"></div>
</aside>
CSS
body {
background: #f3f3f3;
margin: 0;
padding: 0;
}
#page-wrapper {
width: 90%;
margin: 0 auto;
overflow: hidden;
}
#sidebar {
width: 30%;
float: left;
background: #ffffff;
padding: 10px;
height: 100%;
position: fixed;
}
#main {
width: 60%;
float: right;
}
#side-content-1, #side-content-2 {
height: 400px;
}
#side-content-1 {
background: red;
opacity: 0.4;
}
#side-content-2 {
background: green;
opacity: 0.4;
margin-top: 10px;
}
EDIT
Bare in mind content in sidebar sums up to less than one of the page content, so once it reaches the bottom (so when bottom shadow disappears) it should stay there, while main content can be still scrolled down.
This is still a little rough, but its a start:
I went through and polished it a little more and took care of some window resizing issues.
I think this will work for you:
Updated Working Example
JS
$(window).scroll(function () {
var y = $(window).scrollTop();
var x = $(window).scrollTop() + $(window).height();
var s = $('#sidebar').height();
var o = $('#side-content-1').offset().top;
var q = $('#side-content-1').offset().top + $('#side-content-1').height();
var u = $('#side-content-2').offset().top;
if (x > s) {
$('#sidebar').css({
'position': 'fixed',
'bottom': '0',
'width': '27%'
});
$('#bottomShadow').hide();
}
if (x < s) {
$('#sidebar').css({
'position': 'static',
'width': '30%'
});
$('#bottomShadow').show();
}
if (y > o) {
$('#topShadow').show().css({
'position': 'fixed',
'top': '-2px'
});
}
if (y < o) {
$('#topShadow').hide();
}
if (y > q - 4 && y < q + 10) {
$('#topShadow').hide();
}
if (x > u - 10 && x < u + 4) {
$('#bottomShadow').hide();
}
});
var shadow = (function () {
$('#topShadow, #bottomShadow').width($('#sidebar').width());
});
$(window).resize(shadow);
$(document).ready(shadow);
CSS
body {
background: #f3f3f3;
margin: 0;
padding: 0;
}
#page-wrapper {
width: 90%;
margin: 0 auto;
overflow: hidden;
}
#sidebar {
width: 30%;
float:left;
background: #ffffff;
padding: 10px;
}
#main {
width: 60%;
float: right;
}
#side-content-1, #side-content-2 {
height: 400px;
}
#side-content-1 {
background: red;
opacity: 0.4;
}
#side-content-2 {
background: green;
opacity: 0.4;
margin-top: 10px;
}
#topShadow {
display:none;
height:2px;
box-shadow:0px 5px 4px #000;
}
#bottomShadow {
position:fixed;
bottom:-3px;
height:2px;
width:99%;
box-shadow:0px -5px 4px #000;
}
CSS Tricks website have an article on Persistent Headers where they accomplish something similar with a bit of JQuery

Categories

Resources