I'm building multi layered parallax effect on my site (html, css, js). Everything works quite well, but I've noticed that my parallax effect works really bad on Firefox, window.onscroll seems to be laggy, refresh rate so to speak is very low.
Here's my JS implementation:
document.addEventListener('DOMContentLoaded', function() {
const layers = document.querySelectorAll("[data-type='parallax']");
window.addEventListener('scroll', event => {
const topDistance = window.pageYOffset;
window.requestAnimationFrame(function() {
for (let i = 0; i < layers.length; ++i) {
const depth = layers[i].getAttribute('data-depth');
const movement = topDistance * depth;
const translate3d = 'translate3d(0, ' + movement + 'px, 0)';
layers[i].style.transform = translate3d;
}
})
});
});
My html code:
<div class="parallax-banner">
<div class="parallax-layer layer-1" data-type="parallax" data-depth="0.05"></div>
<div class="parallax-layer layer-2" data-type="parallax" data-depth="0.2"></div>
<div class="parallax-layer layer-3" data-type="parallax" data-depth="0.4"></div>
<div class="parallax-layer layer-4" data-type="parallax" data-depth="0.6"></div>
<div class="parallax-layer layer-5" data-type="parallax" data-depth="0.7"></div>
<div class="parallax-layer layer-6" data-type="parallax" data-depth="0"></div>
</div>
Have you encountered it? Is it typical issue? How can I fix that?
I have the following very simple JS-implementation with two layers in the back, which was unusable with Firefox due to jittering and laggy behaviour:
$(function() {
$(window).on('scroll', function() {
$('#background').css('background-position-y', $(window).scrollTop() * -.15);
});
});
$(function() {
$(window).on('scroll', function() {
$('#background2').css('background-position-y', $(window).scrollTop() * -.09);
});
});
CSS-only alternatives didnt work for me as it caused the background layers to visibly overflow after my contents end.
Finally I found a way to improve the performance desktop Firefox (not on mobile yet). I added
position: fixed;
background-attachment: fixed;
background-position: top;
to all my background layers.
Still no improvement in iOS Safari and mobile Firefox.
There are several bug reports for Firefox since version 16.
On my long way searching the internet for solutions i also found and added a script by keithclark but I'm not sure if this makes any difference at all, the script is from 2011:
/*
Firefox super responsive scroll (c) Keith Clark - MIT Licensed
*/
(function(doc) {
console.log("Document executed")
var root = doc.documentElement,
scrollbarWidth, scrollEvent;
// Not ideal, but better than UA sniffing.
if ("MozAppearance" in root.style) {
// determine the vertical scrollbar width
scrollbarWidth = root.clientWidth;
root.style.overflow = "scroll";
scrollbarWidth -= root.clientWidth;
root.style.overflow = "";
// create a synthetic scroll event
scrollEvent = doc.createEvent("UIEvent")
scrollEvent.initEvent("scroll", true, true);
// event dispatcher
function scrollHandler() {
doc.dispatchEvent(scrollEvent)
}
// detect mouse events in the document scrollbar track
doc.addEventListener("mousedown", function(e) {
if (e.clientX > root.clientWidth - scrollbarWidth) {
doc.addEventListener("mousemove", scrollHandler, false);
doc.addEventListener("mouseup", function() {
doc.removeEventListener("mouseup", arguments.callee, false);
doc.removeEventListener("mousemove", scrollHandler, false);
}, false)
}
}, false)
// override mouse wheel behaviour.
doc.addEventListener("DOMMouseScroll", function(e) {
// Don't disable hot key behaviours
if (!e.ctrlKey && !e.shiftKey) {
root.scrollTop += e.detail * 16;
scrollHandler.call(this, e);
e.preventDefault()
}
}, false)
}
})(document);
You can test it by pasting it to the console.
I hope I could at least help a little bit.
Related
I'm attempting to produce kind of like a Fullscreen experience while on landscape mode but nothing seems to work so far. I got a video that fakes going fullscreen mode as I have my own controls, unfortunately iOS doesn't support entering fullscreen on nothing except HTML5 <video> tags, so since I'm forced to use Fullscreen API and can't control those native buttons' visibility while on fullscreen state, I need to do something else which is faking it.
I want to produce the same effect as when going to "Aa button" on Search Bar > Hide Toolbar. It collapses everything and shows almost the entire screen space.
I tried going the CSS way or the window.scrollTo(0, 1) trick. Not working in my case as I have a fixed non-scrollable container holding the video and occupying the entire screen.
Also tried with this and many other codepens & jsfiddles on my Safari, none worked.
/*!
Intelligent auto-scrolling to hide the mobile device address bar
Optic Swerve, opticswerve.com
Documented at http://menacingcloud.com/?c=iPhoneAddressBar
*/
var bodyTag;
var executionTime = new Date().getTime(); // JavaScript execution time
// Document ready
//----------------
documentReady(function() {
// Don't hide address bar after a distracting amount of time
var readyTime = new Date().getTime()
if((readyTime - executionTime) < 3000) hideAddressBar(true);
});
// Run specified function when document is ready (HTML5)
//------------------------------------------------------
function documentReady(readyFunction) {
document.addEventListener('DOMContentLoaded', function() {
document.removeEventListener('DOMContentLoaded', arguments.callee, false);
readyFunction();
}, false);
}
// Hide address bar on devices like the iPhone
//---------------------------------------------
function hideAddressBar(bPad) {
// Big screen. Fixed chrome likely.
if(screen.width > 980 || screen.height > 980) return;
// Standalone (full screen webapp) mode
if(window.navigator.standalone === true) return;
// Page zoom or vertical scrollbars
if(window.innerWidth !== document.documentElement.clientWidth) {
// Sometimes one pixel too much. Compensate.
if((window.innerWidth - 1) !== document.documentElement.clientWidth) return;
}
// Pad content if necessary.
if(bPad === true && (document.documentElement.scrollHeight <= document.documentElement.clientHeight)) {
// Extend body height to overflow and cause scrolling
bodyTag = document.getElementsByTagName('body')[0];
// Viewport height at fullscreen
bodyTag.style.height = document.documentElement.clientWidth / screen.width * screen.height + 'px';
}
setTimeout(function() {
// Already scrolled?
if(window.pageYOffset !== 0) return;
// Perform autoscroll
window.scrollTo(0, 1);
// Reset body height and scroll
if(bodyTag !== undefined) bodyTag.style.height = window.innerHeight + 'px';
window.scrollTo(0, 0);
}, 1000);
}
// Quick address bar hide on devices like the iPhone
//---------------------------------------------------
function quickHideAddressBar() {
setTimeout(function() {
if(window.pageYOffset !== 0) return;
window.scrollTo(0, window.pageYOffset + 1);
}, 1000);
}
Any clue on how to produce this "fullscreen landscape" effect on mobile browser (Safari/Chrome) ?
Dragging with hammer.js works on browser on pc. It fires continuously but when it comes to iPad, there is little problem. It fires after dragging/swiping finger stops on screen
template
<v-sheet v-pan="onPan">
<div class="vista__img" ref="image" :style="{ backgroundImage: 'url(' + vista.image + ')' }"></div>
</v-sheet>
directives
directives:{
pan: {
bind: function(el, binding) {
if (typeof binding.value === "function") {
const mc = new Hammer(el);
mc.get("pan").set({ direction: Hammer.DIRECTION_ALL });
mc.on("pan", binding.value);
}
}
},
methods && computed
methods:{
onPan(e) {
const dragOffset = -100 / this.itemWidth * e.deltaX / this.overflowRatio;
const transform = this.currentOffset + dragOffset;
this.$refs.image.style.setProperty("--x", transform);
if (e.isFinal) {
this.currentOffset = transform;
const maxScroll = 100 - this.overflowRatio * 100;
let finalOffset = this.currentOffset;
}
},
}
computed: {
overflowRatio() {
return this.$refs.image.scrollWidth / this.$refs.image.offsetWidth;
},
itemWidth() {
return this.$refs.image.scrollWidth;
},
},
Any idea about handling drag continuously on iPad?
AND this problem only occurs on iPad browsers(safari, chrome), there is no problem on iPhone
Your issue can be related to a phenomenon called event propagation. iOS handles it a little bit differently than other systems (yes, it's system-specific, not browser-specific and Peter-Paul Koch wrote an article about it).
If you want to get a little bit of background, javascript.info has a great, extensive article about bubbling and capturing.
And if you just want to solve the problem, here's an issue on GitHub that should help. Also, hammer.js documentation mentions it as well.
I wrote this code to add swipe function for an image slider. The slider is working correctly.
However when i perform a right or left swipe there is some vertical scrolling which is distracting and annoying.
I'm storing the reference to touchstart in the touch object.
And on touchend event, if vertical distance (lenY) is more than 50, i trigger preventDefault on the touchstart.
This isn't working.
Simplest option is to call preventDefault directly on touchStart. But the image slider occupies a large part of the mobile screen making scrolling down the page tricky.
I need to pass the lenY (vertical distance) to the touch start handler to prevent default action.
function triggerTouch() {
"use strict";
var tZone = document.getElementById('sl-m'),
touch = {},
startX = 0,
startY = 0,
endX = 0,
endY = 0;
if (tZone) {
tZone.addEventListener('touchstart', function (e) {
startX = e.changedTouches[0].screenX;
startY = e.changedTouches[0].screenY;
// store reference to touch event
touch.start = e;
}, false);
tZone.addEventListener('touchend', function (e) {
endX = e.changedTouches[0].screenX;
endY = e.changedTouches[0].screenY;
var lenX = Math.abs(endX - startX);
var lenY = Math.abs(endY - startY);
// check if user intended to scroll down
if (lenY < 50 && lenX > 50) {
touch.start.preventDefault();
e.preventDefault();
swipe(tZone, startX, endX);
}
}, false);
}
}
Since i haven't got an answer i am posting my own answer, hoping someone can provide the correct implementation.
I ended up using the css overflow property to temporarily disable vertical scroll.
This works perfectly though there is a small side effect. Once you swipe through the image slider, the scroll is disabled.
A swipe upwards is required to restore scroll to the page. Its not noticeable but i still want to figure the right way.
var touch = {};
window.onload = function () {
"use strict";
document.body.addEventListener("touchstart", touchHandler);
document.body.addEventListener("touchend", touchHandler);
};
function touchHandler(e) {
"use strict";
var el = e.target;
if (el.parentNode.id === "sl-m") {
if (e.type === "touchstart") {
touch.startX = e.changedTouches[0].screenX;
touch.startY = e.changedTouches[0].screenY;
} else {
touch.endX = e.changedTouches[0].screenX;
touch.endY = e.changedTouches[0].screenY;
touch.lenX = Math.abs(touch.endX - touch.startX);
touch.lenY = Math.abs(touch.endY - touch.startY);
if (touch.lenY < 20) {
// disable scroll
document.body.style.overflowY = "hidden";
// do swipe related stuff
swipe(el.parentNode);
} else {
// enable scroll if swipe was not intended
document.body.style.overflowY = "scroll";
}
}
} else {
// keep scroll enabled if touch is outside the image slider
document.body.style.overflowY = "scroll";
}
}
I want to share the solution that works for me. The above solution did not work on ios. I am sorry for my English. I do not know english.
function stop(e){
e=e || event;
e.preventDefault;
}
window.onscroll=stop(); //-->Yes, we will use it ..
For example, where you will use;
function move(event){
var finish=event.touches[0].clientX;
var verticalFinish=event.touches[0].clientY;
var diff=finish-strt;
var verticalDiff=verticalStrt-verticalFinish;
var f;
if(diff<0 && (Math.abs(diff)>Math.abs(verticalDiff)/3)){
f=verticalDiff+widthOffset;
slayt[x].style.left=diff+"px";
slayt[x].style.transition="none";
slayt[y].style.left=f+"px";
slayt[y].style.transition="none";
window.onscroll=stop(); //-->we used it here :)
}
else if(diff>0 && (Math.abs(diff)>Math.abs(verticalDiff)/3)){
f=diff-widthOffset;
slayt[x].style.left=diff+"px";
slayt[x].style.transition="none";
slayt[z].style.left=f+"px";
slayt[z].style.transition="none";
window.onscroll=stop();//-->we used it here :)
}
}
but there is a small problem. cancels if there is another function related to scrolling. return true; it does not work. I also write twice if I have a function related to the slider inside and outside the touchend.
function end(event){
//"touchend" related codes...
//bla bla
window.onscroll=function(){m=window.pageYOffset;console.log(m);if(m>=850)
{buton.style.display="block";}else{buton.style.display="none";}}
}
If it is useful, I will be happy...
Update :
I typed wrong. I want to fix. Actually, the scroll event cannot be canceled unfortunately. So the event we canceled above, scroll is not a vertical scroll event. All events.
window.onscroll=stop(); // ==>improper use
stop(); // ==> actually - Correct usage
It just needs to be written so stop().
html,
body {
overflow: hidden;
}
Did you try this?
I wrote a program in which you drag my custom scroll bar and page scrolls accordingly, everything works fine, however while testing it I discovered that if the page is too big 20 000 pixels and you do a large scroll it will crash chrome and opera.
This is caused by window.scroll(0, whereto1); being fired too many times on drag. when i comment this line of code everything works on the large scroll obviously, except for the functionality of scrolling page while dragging.
Below is the relevant code, if anyone has any good idea on how to do this without crashing the browser on large pages let me know. Thank you very much for your time.
var $dragging = null;
var pageheight1 = $( document ).height();
$(document.body).on("mousemove", function(e) {
if ($dragging) {
var wrap = jQuery('.pvs_inner_fixedbar');
wrapoffset = wrap.offset();
wrapbottomborder = ( wrapoffset.top + wrap.height() ) - jQuery('.pvs_scroll_marker').height();
var pos = e.pageY;
if(pos < wrapoffset.top){
pos = wrapoffset.top;
}
if(pos > wrapbottomborder){
pos = wrapbottomborder;
}
$dragging.offset({
top: pos
});
var ratio1 = pageheight1 / 400;
var whereto1 = Math.round((pos - wrapoffset.top) * ratio1) - 100;
window.scroll(0, whereto1); // FIRES TOO MANY TIMES :((((
}
});
$(document.body).on("mousedown", ".pvs_scroll_marker", function (e) {
$dragging = $(e.target);
});
$(document.body).on("mouseup", function (e) {
$dragging = null;
});
You can throttle calls to window.scroll() called from mousemove event handler
https://remysharp.com/2010/07/21/throttling-function-calls
I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.