Why does this webworker cause these animations to lag? - javascript

I have set up a webworker to run an intensive calculation. On my main page, I have a spinning icon that is animated. However, when the webworker runs, my spinning icon lags. It seems to be browser dependent as well. I believe this is because despite using a separate js thread, the browser is still demanding too many resources from the computation, but I am not sure. Does anyone know why this happens? Or how I can fix it? Short of using a backend, I have no idea what to do.
Full example: https://codesandbox.io/s/modest-bohr-56u5k7?file=/src/App.js
App.js:
import { FaBeer } from "react-icons/fa"; // an icon
function App() {
const myWorker = new Worker('Worker.js');
myWorker.onmessage = function (e) {
console.log('message received from worker', e.data);
};
function work() {
myWorker.postMessage(3000000);
}
return (
<div>
<FaBeer className="spinner"/>
</div>
);
}
Worker.js:
onmessage = function (e) {
let n = 0;
while (n < e.data) {
n++;
}
postMessage("done");
};
Styles.css:
.spinner {
animation: spin infinite 2s linear;
margin-left: 5px;
}
#keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

Related

I have a js script which should disable a button but it's not working

I have a button which makes an image spin for 3s. I want the button to be disabled during the spinning. But this is not working. Below are the js functions and the css class which creates the spin.
<script>
function spin() {
document.getElementById("spin_switch").disabled = true;
document.getElementById("image-map").classList.toggle("spin");
setTimeout(stop_spin, 3000);
document.getElementById("spin_switch").disabled = false;
}
function stop_spin() {
document.getElementById("image-map").classList.toggle("spin");
}
</script>
<style>
.spin {
transform: rotate(360deg);
webkit-transform: rotate(360deg);
overflow: hidden;
transition-duration: 3s;
transition-property: transform;
}
</style>
You have to move this line
document.getElementById("spin_switch").disabled = false;
into the stop_spin function:
function spin() {
document.getElementById("spin_switch").disabled = true;
document.getElementById("image-map").classList.toggle("spin");
setTimeout(stop_spin, 3000);
}
function stop_spin() {
document.getElementById("image-map").classList.toggle("spin");
document.getElementById("spin_switch").disabled = false;
}
Otherwise the spin_switch will be enabled again immediately. setTimeout does not stop the entire script. It just registers a callback to be executed after the given timeout has expired.

Why does the 2nd script prevent the 1st script from redirecting and how can I fix it?

Everything works properly EXCEPT the redirection. If I remove the 2nd script the redirection works. Is there something I need to do to get both working?
<script>
function check_frame() {
if( top === self ) { // not in a frame
location.href = "https://xyz.any"; // either the frameset or an error.
}
}
</script>
<script>
function check_frame() {
if( top !== self ) { // in a frame
document.body.classList.add('framed');
}
}
</script>
<body onLoad="check_frame()">
</body>
<style>
body {
opacity: 0;
transition: opacity 2s ease-in;
}
body.framed {
opacity: 1;
}
</style>
You don't need (actually, it does not make sense) two definitions of the same function. Just wrap up in an single implementation, like this:
function check_frame() {
if( top === self ) { // not in a frame
location.href = "https://xyz.any"; // either the frameset or an error.
}
else {
document.body.classList.add('framed');
}
}

Execute code after the lack or eventual completion of a CSS transition

I'm trying to execute some code immediately after a CSS transition finishes. The problem is that in certain cases (not determined by me), the transition does not have to occur. How do I know when to expect a transition? I know about the transitionrun and transitionstart events and I tried to use them, but they don't do the job:
function animateIn (elem, action) {
var expecting = false
setTimeout(() => {
console.log('timeout', expecting)
}, 0)
window.requestAnimationFrame(() => {
console.log('raf', expecting)
})
elem.addEventListener('transitionrun', () => {
expecting = true
console.log('transitionrun')
})
elem.addEventListener('transitionstart', () => {
expecting = true
console.log('transitionstart')
})
elem.addEventListener('transitionend', () => {
console.log('transitionend')
})
action()
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
animateIn(elem, () => {
elem.classList.remove('is-enter')
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
As you can see, I want to use the expecting variable to check whether a transition will start after whatever happens in the action function. And that's what I basically want - to check if a certain action (the removal of a class in this case) causes a transition to run.
My idea was to use requestAnimationFrame or setTimeout to wait for a little bit and if transitionrun or transitionstart has fired in the meantime - then a transition is expected, otherwise - it's not.
The problem with that is the fact that both requestAnimationFrame and setTimeout run before transitionrun and transitionstart had the chance to fire (at least in Chrome). So it appears that the transition takes longer to start than one render loop? Why is that?
One workaround is to use an arbitrary timeout that gives the transition enough time to eventually start:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionstart', startHandler)
setTimeout(() => {
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
}, 100)
action()
})
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
When you click the box for the first time, the Promise waits for the transition to finish and resolves with true to signal that a transition has happened. If you click it more times after that, it resolves with false because no transition occurred. That's exactly what I need.
The problem is setTimeout. That 100ms wait is completely arbitrary and very error-prone, I suppose. Is there a better way to capture this? I need some sort of way to fire code immediately after a transitionstart might occur. I was hoping that requestAnimationFrame would do this but it doesn't. Why?
It appears to work in Chrome (latest), Firefox (latest) and IE11 with a double requestAnimationFrame, even for delayed transitions:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionrun', startHandler)
elem.addEventListener('transitionstart', startHandler)
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
elem.removeEventListener('transitionrun', startHandler)
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
})
})
action()
})
}
function bind (elem) {
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
}
bind(document.getElementById('main'))
bind(document.getElementById('delayed'))
.target {
display: inline-block;
width: 80px;
height: 80px;
margin-right: 5px;
background: red;
}
.target.is-enter {
background: blue;
}
#main {
transition: background 0.5s ease;
}
#delayed {
transition: background 0.5s ease 0.4s;
}
<div id="main" class="target is-enter"></div>
<div id="delayed" class="target is-enter"></div>
I think it's far better than an arbitrary timeout but I still have doubts whether it's reliable. To answer that, I think we need to answer first why one requestAnimationFrame call is not enough in Chrome.
Edit: This trick doesn't seem to work in Chrome on mobile.

Make a background fade in every time "onclick"

I would like the DIV to show a confirmed message where the css effect "animation" or "fade out" is activated with each click. It works fine on the first click, but not on the clicks that follow.
function clientedetail() {
document.getElementById("guardadoC").innerHTML = "Guardado.";
document.getElementById("guardadoC").style.cssText = "animation: background-fade 3s;padding:5px;";
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" onclick="clientedetail()"></input>
<div id="guardadoC"></div>
You can add a addEventListener('animationend', function() { ... }); to reset the animation so you can run it again.
It's also a good idea to keep your CSS into your CSS file and not write it as a strings in JavaScript. Now, we are adding a class to the element to do what we want.
function clientedetail() {
var el = document.getElementById("guardadoC");
el.innerHTML = "Guardado.";
el.classList.add("animating");
//This function runs when the CSS animation is completed
var listener = el.addEventListener('animationend', function() {
el.classList.remove("animating");
//this removes the listener after it runs so that it doesn't get re-added every time the button is clicked
el.removeEventListener('animationend', listener);
});
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
#guardadoC {
padding:5px;
}
#guardadoC.animating {
animation: background-fade 3s;
}
<button type="button" onclick="clientedetail()">click me</button>
<div id="guardadoC"></div>
You can use the animationend event to reset the animation.
The animationend event is fired when a CSS Animation has completed
(but not if it aborts before reaching completion, such as if the
element becomes invisible or the animation is removed from the
element).
You'll notice in this demo that I'm not using anonymous functions. With anonymous functions, we end up redefining the function over and over, which is not what you want regarding performance. Using a functional reference, we declare a function once and tie an event to it.
const btn = document.querySelector(".myButton");
const guardadoC = document.getElementById("guardadoC");
btn.addEventListener("click", clientedetail);
function clientedetail() {
guardadoC.innerHTML = "Guardado.";
guardadoC.style.cssText = "animation: background-fade 3s;padding:5px;";
}
function resetAnimation() {
guardadoC.innerHTML = "";
guardadoC.style.cssText = "";
}
guardadoC.addEventListener("animationend", resetAnimation);
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" class="myButton">
<div id="guardadoC"></div>
jsFiddle
More about animationend
You could recreate the element each time you click the button. This will be a complete reset, and so it will even work when you interrupt the previous animation.
function clientedetail() {
var elem = document.getElementById("guardadoC");
var newElem = elem.cloneNode(true);
elem.parentNode.replaceChild(newElem, elem);
newElem.innerHTML = "Guardado.";
newElem.style.cssText = "animation: background-fade 3s;padding:5px;";
}
#keyframes background-fade {
0% {
background-color: green;
}
100% {
background-color: none;
}
}
<input type="button" onclick="clientedetail()"></input>
<div id="guardadoC"></div>
Trigger it based on class if you can, the way you're doing it it will only do it once.
Or you can destroy the element and re-create it kinda like this.
function clientedetail() {
var element = document.getElementById("guardadoC");
if (typeof(element) != 'undefined' && element != null)
{
document.getElementById("guardadoC").remove();
var remakeDiv = document.createElement("div");
remakeDiv.setAttribute("id", "guardadoC");
document.body.appendChild(remakeDiv)
}
document.getElementById("guardadoC").innerHTML = "Guardado.";
document.getElementById("guardadoC").style.cssText = "animation: background-fade 3s;padding:5px;";
}

pause and resume setInterval in javascript

I am trying to mimic the typing effect on the codility's home page in JavaScript.I have already achieved the typing and deleting effect
using setInterval().
Here's the jsfiddle of that:
https://jsfiddle.net/yzfb8zow/
var span=document.getElementById("content");
var strings=["hello world","how r u??"];
var index=0; //get string in the strings array
var chIndex=0; //get char in string
var type=true;
setInterval(function(){
if(index===strings.length)
index=0;
if(type){
typeIt();
}
else
deleteIt();
},200);
// type the string
function typeIt(){
if(chIndex<strings[index].length)
span.innerHTML=strings[index].substring(0,chIndex++);
else
type=false;
}
//delete the string
function deleteIt(){
if(chIndex===0){
index++;
type=true;
}
else
span.innerHTML=strings[index].substring(0,chIndex--);
}
the html
<span id="content"></span>
<span id="cursor">|</span>
the css
#cursor{
-webkit-animation: 1s blink step-end infinite;
-moz-animation: 1s blink step-end infinite;
animation: 1s blink step-end infinite;
}
#keyframes blink {
from, to {
opacity:0;
}
50% {
opacity: 1;
}
}
#-moz-keyframes blink {
from, to {
opacity:0;
}
50% {
opacity:1;
}
}
#-webkit-keyframes blink {
from, to {
opacity:0;
}
50% {
opacity:1;
}
}
What I can't get my head around is how could I pause the setInterval function at the beginning of string change and at the end to get a clear blinking cursor.
I have looked up other answers Pause and resume setInterval and How do I stop a window.setInterval in javascript? but I am unable to comprehend how to use it in this context.
Also it would be great if you could tell me how to improve my code.
I edited your code here is your jsfiddle back: https://jsfiddle.net/yzfb8zow/5/
Also a little code explanation
var typingFunction = function() {
if (index === strings.length) {
index = 0;
}
if (type) {
typeIt();
} else {
deleteIt();
}
}
I declared your previous typing function to later be able to use it as desired.
var x = setInterval(typingFunction.bind(typingFunction), 200);
Stored the interval so I can clear it later - stored in a global variable (not necessarily ok and there are other solutions but store it).
function typeIt() {
if (chIndex == -1) {
chIndex += 1;
clearAndRestartInterval(1000);
return;
}
if (chIndex <= strings[index].length) {
span.innerHTML = strings[index].substring(0, chIndex++);
} else {
clearAndRestartInterval(1000);
type = false;
return;
}
}
In the typeIt function I clear interval at the begining at chIndex = -1, wait a while so letting the cursor blink before I restart the interval. Than I clear it again at the end of the string. My chIndex starts from -1 so I know when to blink at the begining.
Rest of the code is self explanatory.
Feel free to edit the parameter of the clearAndRestartInterval function in order to set the time of the blinking at the begining and the end.
function clearAndRestartInterval(timeOfRestart) {
clearInterval(x);
setTimeout(function() {
x = setInterval(typingFunction.bind(typingFunction), 200);
}, timeOfRestart);
}
Last but not least the stop and restart function of the interval. This is quite simple and x - is the previously declared setInterval - globally which I clear and reset with a new interval ofter some time.
Hope this helps.
Cheers
You will have to use nested timeouts.
Call typeIt directly, as its a default action.
In typeIt, add a setTimeout which will call deleteIt on completion.
On completion of deleteIt, call typeIt again.
Sample Fiddle
You typeit will look something like this:
function typeIt() {
var t_interval = setInterval(function() {
if (chIndex <= strings[index].length)
span.innerHTML = strings[index].substring(0, chIndex++);
else {
type = false;
// This is to be executed once action is completed.
window.clearInterval(t_interval);
}
}, 20)
}

Categories