window.scroll smooth not working on Safari - javascript

I'm using this to get a smooth scroll to a particular section when a user clicks on a button:
window.scroll({
top: $(this).data('y'),
left: 0,
behavior: 'smooth'
});
This works great everywhere (including Android phones) but on Safari (desktop and iphone). In those cases it scrolls to the correct position but it isn't smooth, it's like it jumps.
I made a small demo on Codepen available here. Just click on the nav menu options and it will scroll there. This will be smooth on Chrome but not on Safari.
Is this not supported? (it doesn't seem to be the case looking at the doc) What are the supported options?
Thanks!

window.scroll is supported, but scroll-behavior CSS is not.
https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior
Pending support, consider using the smoothscroll-polyfill which adds smooth scrolling support for Safari, IE, and Edge.

I recently wrote down my ideas into a function, which is kind of polyfill for lacking smooth scroll feature support in IOS browsers and desktop Safari as well. Some may call it a bloody workaround, but hey, it's working. It doesn't require jQuery, it's pure javascript.
var fnc_scrollto = function(to,id){
var smoothScrollFeature = 'scrollBehavior' in document.documentElement.style;
var articles = document.querySelectorAll("ul#content > li"), i;
if (to == 'elem') to = articles[id].offsetTop;
var i = parseInt(window.pageYOffset);
if ( i != to ) {
if (!smoothScrollFeature) {
to = parseInt(to);
if (i < to) {
var int = setInterval(function() {
if (i > (to-20)) i += 1;
else if (i > (to-40)) i += 3;
else if (i > (to-80)) i += 8;
else if (i > (to-160)) i += 18;
else if (i > (to-200)) i += 24;
else if (i > (to-300)) i += 40;
else i += 60;
window.scroll(0, i);
if (i >= to) clearInterval(int);
}, 15);
}
else {
var int = setInterval(function() {
if (i < (to+20)) i -= 1;
else if (i < (to+40)) i -= 3;
else if (i < (to+80)) i -= 8;
else if (i < (to+160)) i -= 18;
else if (i < (to+200)) i -= 24;
else if (i < (to+300)) i -= 40;
else i -= 60;
window.scroll(0, i);
if (i <= to) clearInterval(int);
}, 15);
}
}
else {
window.scroll({
top: to,
left: 0,
behavior: 'smooth'
});
}
}
};
You may pass arguments to the function as numeric value (scollTo-position in pixels) or as a call of an element with index (in my case LI nodes in an UL --> "articles").
<a class="active" href="javascript:fnc_scrollto(0)">Home</a>
<a class="active" href="javascript:fnc_scrollto(457)">anywhere</a>
element 1
element 2
You may play around with the numbers to adapt the smooth effect to your needs.
If you have a sticky navbar on top, you need to adapt the line
if (to == 'elem') to = articles[id].offsetTop;
to your needs like e.g.
if (to == 'elem') to = parseInt(articles[id].offsetTop-navbar.clientHeight);
Hope you like it :-)

More specifically in a JavaScript context, the unsupported part is the behavior parameter on scrollToOptions as detailed here:
https://developer.mozilla.org/en-US/docs/Web/API/Window/scroll

Related

How to wait till animation finish in pure javascript

Im trying to make an infinite loop for a carousel when i click a desired div it will be centered at a desired position, if i click the item that is adjacent to the centered position, everything works fine, but if he is more than 1 position away from the center it triggers an effect that doesnt follow the usual logic.
I have tried to solve the problem by checking the distance from the center and than moving the items 1 by 1 n times, but i guess because the loop doesn't wait for the animations to finish im getting this weird effect.
The final outcome should be making an infinite feel to the carousel when you click an item that is 5 positions away from the center it would center it and the ones that are out of view will slide from the respective direction to create a loop
any help will be appreciated, Im relatively new to web dev so a well explained answer will be highly appreciated
JS:
const serviceList = document.querySelectorAll('.service__block');
serviceList.forEach(service => {
service.addEventListener('click', () => {
markSelectedService(service);
moveService(checkDistance(service));
});
});
//Adds the class to the clicked service
function markSelectedService(service) {
removeSelectedClass();
service.classList.add('selected');
}
//Removes the selected class from all the services
function removeSelectedClass() {
serviceList.forEach(service => {
service.classList.remove('selected');
});
}
//Check distance from center
function checkDistance(service) {
let distance = service.dataset.order - 4;
return distance;
}
//Move the service 1 by 1 n times
function moveService(distance) {
if (distance > 0) {
for (var i = 0; i < distance; i++) {
serviceList.forEach(service => {
service.dataset.order = parseInt(service.dataset.order) - 1;
if (parseInt(service.dataset.order) === -1) {
service.dataset.order = 11;
}
});
}
} else if (distance < 0) {
distance = distance * -1;
for (var i = 0; i < distance; i++) {
serviceList.forEach(service => {
service.dataset.order = parseInt(service.dataset.order) + 1;
if (parseInt(service.dataset.order) === 12) {
service.dataset.order = 0;
}
});
}
}
}
Link to codepen:
https://codepen.io/tomyshoam/pen/yLLLYyQ
If you want to wait for the animation's end to trigger the next one you insert a setInterval and set the interval time to your animation's time.
Example:
var i = 0;
var animationInterval = setInterval(function () {
//YOUR LOGIC HERE
if (++i === distance) {
window.clearInterval(animationInterval);
}
}, 500);
Update
After some tries I think I got it working how you wanted, although it's not the better way to do it.
I copied the icons list, one to the right side and one to the left, this way the icons will only move to the other side when they are far far away from the user's view and will not trigger that weir behavior.
Code below:
https://codepen.io/diogoperes/pen/oNNNBGY

javascript - make setTimeout faster?

My code is:
function slide(x)
{
if (x==undefined)
var x = 1;
if (x >= 4096)
return;
document.getElementById("slide").style.backgroundPosition = x + "px 0px";
x++;
setTimeout(function() {
slide(x);
}, 1);
}
JSFIDDLE
It makes a spin (?) by changing backgroundPosition, and it works. But it's too slow, I'd want to make it faster, and then gradually slow down. How can I do that?
You should pick a higher delay than 1ms. In most browsers 10 to 50 ms would be a lot more reliable. To speed up your animation though, increase x increments. For example:
function slide(x)
{
if(x==undefined) var x = 1;
if(x >= 4096) return;
document.getElementById("slide").style.backgroundPosition = x+"px 0px";
x += 10; // or some other value
setTimeout(function() {
slide(x);
}, 50); // or some other value
}
Also, you probably want to check x like this:
if (typeof x === 'undefined') { x = 1; }, no need for var.
2018 UPDATE:
Check out the https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame API. Using this over a fixed update interval is usually preferable.
I have rewrite all the function:
function slide() {
var x = 1;
var y = 30;
var clr = setInterval(function(){
if(x >= 4096) x = 1;
document.getElementById("slide").style.backgroundPosition = x+"px 0px";
x+=y;
y-=0.1;
if (y<=0) { clearInterval(clr); }
},10);
}
https://jsfiddle.net/tatrwkmh/4/
Currently the position is being changed by 1 pixel every time slide is called, via the line x++;. You can make it faster by changing this from x++ to x += 2 or x += 3 etc.
Your animation may look clunky without some sort of easing function, though. You should look into using some sort of animation library instead.
I got it nicely starting fast and then going slower by adding to your code the following:
if(x < 1000)
x+=2
else if(x < 1500)
x+=1.5
else
x++;

Chrome high cpu use when multiples canvas are running at same time

I’m having a problem here since a couple days ago with Google Chrome. I love pixel art and I'm trying to create a huge animated map using html5.
Everything was ok until I started to use more than 5 or 6 canvas at same time.
In Firefox and Internet explorer the map has no problems, but in Chrome the CPU reaches 70% and sometimes even more.
Can you give me some good tips about how solve this kind of issues in Chrome?
I've been searching on Internet about improve performance in Chrome but nothing is helping me.
Thanks for your help.
Just for reference this is the map:
http://pixelslivewallpaper.github.io/jadsdsengine/Zelda.htm
Solution:
The easy way to fix this problem is stopping the animation of all the canvas outside the user visible area and resuming them later. The way for calculate this area is capturing the scroll position.
Apparently, Firefox and Internet explorer are doing this job automatically, but in Google Chrome you need do it manually.
Here is the code:
this.loadIsVisibleOptions = function () {
if (this.stopAnimationWhenIsNotVisible) {
window.addEventListener("scroll", function (event) { currentJadsdsEngine.isVisible(document.getElementById(canvasID), currentJadsdsEngine) });
}
}
this.isVisible = function (element, obj) {
if (element.offsetWidth === 0 || element.offsetHeight === 0) {
currentJadsdsEngine.stop();
}
else {
var rects = element.getClientRects();
var result = false;
var tempClientHeight = document.documentElement.clientHeight;
var tempClientWidth = document.documentElement.clientWidth;
for (var i = 0, l = rects.length; i < l; i++) {
if (rects[i].top + rects[i].height > 0 ? rects[i].top <= tempClientHeight : (rects[i].bottom > 0 && rects[i].bottom <= tempClientHeight) == true) {
if (rects[i].left + rects[i].width > 0 ? rects[i].left <= tempClientWidth : (rects[i].right > 0 && rects[i].right <= tempClientWidth) == true) {
result = true;
break;
}
}
}
if (result) {
currentJadsdsEngine.resume();
}
else {
currentJadsdsEngine.stop();
}
}
}
Inspiration:
How do I check if an element is really visible with JavaScript?

timing and delays on javascript

Im changing the page background of a from white to black following a sequence of 1 and 0. At the moment I manage to make it work were the black and white background changes with the same delay:
var syncinterval = setInterval(function(){
bytes = "10101010101010101010101010101010101010101010101010101010101010101";
bit = bytes[i];
output_bit(bit);
i += 1;
if (i > bytes.length) {
clearInterval(syncinterval);
i = 0;
for (i=0; i < input.length; i++) {
tbits = input[i].charCodeAt(0).toString(2);
while (tbits.length < 8) tbits = '0' + tbits;
bytes += tbits;
}
console.log(bytes);
}
}, sync_speed);
Complete working demo: http://jsfiddle.net/kn48Z/
How can I modify the function to make the white background last for sync_speed seconds and the black background for other any value?
If I'm understanding what you're trying to do, try placing your code into a separate function and use setTimeout. This will give you more control over what you're trying to accomplish.
I modified your code a bit. I check to see if bit is 1 (which I'm assuming is white), if it is, it will run backgroundOrSomething within sync_speed seconds. If it's not, it will run backgroundOrSomething within otherTime. I set otherTime to 1 second, just for giggles.
function backgroundOrSomething (){
bytes = "10101010101010101010101010101010101010101010101010101010101010101";
bit = bytes[i];
output_bit(bit);
i += 1;
if(i > bytes.length) {
i = 0;
for (i=0; i < input.length; i++) {
tbits = input[i].charCodeAt(0).toString(2);
while (tbits.length < 8) tbits = '0' + tbits;
bytes += tbits;
}
console.log(bytes);
}
otherTime = 1000;
if (bit == 1)
setTimeout(backgroundOrSomething, sync_speed);
else
setTimeout(backgroundOrSomething, otherTime);
}
setTimeout(backgroundOrSomething, sync_speed);
The last setTimeout at the bottom of the code will be the first to execute backgroundOrSomething.
As above you'll need to use a set interval as follows:
var bit = 0;
var syncInterval = setInterval(function() {
document.body.style.backgroundColor = bit == 0 ? 'black' : 'white';
bit = 1;
}, 3000);
var stopInterval = setTimeout(function() {
clearInterval(syncInterval);
}, 50000);
Fiddle here http://jsfiddle.net/kn48Z/4/. Note slowed down background switch so it should give a fit.

Parsing a number of pixels to an integer

In my code i'm trying to compare the height with the position of an element, to ensure that the element doesn't leave the game's div.
First I get the position of my element, snake. and if the cursor is too close then i move it. Then at the end I do a check that it is at least 20 pixels away from the top and bottom. For some reason everything is working except the when it reaches the bottom of the screen (which is the else if statement in the end block of code)
var posL = $("#snake").position().left;
var posT = $("#snake").position().top;
if((e.pageX-200 < posL) && (posL < e.pageX-50)){
if(posL > 20){
posL = posL - 5;
}else{
posT = posT + 5;
}
...
if(posT < 20){
posT = 20;
}else if(posT > parseInt($("#game").height)){
posT = parseInt($("#game").height) - 20;
}
You forgot the () after .height ...twice! ;-)
if(posT < 20) {
posT = 20; // ------------------------v
} else if(posT > parseInt($("#game").height)){
posT = parseInt($("#game").height) - 20;
} // -----------------^

Categories