Improve multiple setInterval() lag in javascript matrix effect - javascript
I am using multiple setInterval() e.g. to create, move, delete the strings that fall on the screen
Problem is that the MODE 1 interval causes a lag for the interval1
I've also tried to switch to the MODE 2 STUFF but the lag still occurs...
How can this be improved?
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
var chinese = [`電`,`買`,`開`,`東`,`車`,`紅`,`馬`,`無`,`鳥`,`熱`,`時`,`佛`,`德`,`拜`,`黑`,`冰`,`兔`,`妒`,`壤`,`每`,`步`,`聽`,`實`,`證`,`龍`,`賣`,`龜`,`藝`,`戰`,`繩`,`關`,`鐵`,`圖`,`團`,`轉`,`廣`,`惡`,`豐`,`腦`,`雜`,`壓`,`雞`,`價`,`樂`,`氣`,`廳`,`發`,`勞`,`劍`,`歲`,`權`,`燒`,`贊`,`兩`,`譯`,`觀`,`營`,`處`,`學`,`體`,`點`,`麥`,`蟲`,`舊`,`會`,`萬`,`盜`,`寶`,`國`,`醫`,`雙`,`觸`,`參`];
var japanese = ['が','ぎ','ぐ','げ','ご','ゃ','ゅ','ょ','ざ','じ','ず','ぜ','ぞ','だ','づ','で','ど','ぢ','ば','ぶ','べ','ぼ','ぱ','ぴ', 'ぷ','ぺ','ぽ','あ','い','う','え','お','か','き','く','け','こ','き','さ','し','す','せ','そ','し','た','ち','つ','て','と','ち','な','に','ぬ','ね','の','に','は','ふ','へ','ほ','ひ','ま','み','む','め','も','や','ゆ','よ','ら','る','れ','ろ','り','わ','ゐ','ゑ','を'];
var characters = japanese;
var speedL = [60,80,90,100,120,140,160,180,200,220,240,280,300,400,500,600,700,800,810,820,830,840,850];
var speedDown = [4,5,6,7,8,9,10];
// MODE 2 STUFF
/*var timeStmpSleepMs = 480;
var timeStmp = Date.now();*/
function do_string(){
var string = document.createElement('section');
var sd = speedDown[Math.floor(Math.random()*speedDown.length)];
string.style.left = Math.floor(Math.random() * (document.documentElement.clientWidth-80)) + 'px';
var t = -2200;
string.style.top = t+'px';
var interval1 = setInterval(function(){
// MODE 2 STUFF
/*var newTimeStmp = Date.now();
if((newTimeStmp - timeStmp) > 480){
timeStmp = newTimeStmp;
//console.log('timeStmpSleepMs fired');
do_string();
}*/
if(parseInt(string.style.top) >= (document.documentElement.clientHeight-100)){
clearInterval(interval1);
string.remove();
return false;
}
string.style.top = (t += sd) + 'px';
}, 16);
function letter(){
var letter = document.createElement('div');
letter.innerHTML = characters[Math.floor(Math.random()*characters.length)];
string.appendChild(letter);
// CHANGING LETTER & DELETING LETTER
/*var interval2 = setInterval(function(){
if(parseInt(string.style.top) >= (document.documentElement.clientHeight-200)){
clearInterval(interval2);
letter.remove();
return false;
}
letter.innerHTML = characters[Math.floor(Math.random()*characters.length)];
}, speedL[Math.floor(Math.random()*speedL.length)]);*/
}
for(var i=0; i<getRandomInt(1, 180); i++){
letter();
document.body.appendChild(string);
}
}
do_string();
// MODE 1 STUFF
setInterval(function(){
do_string();
}, 480);
body {
font-size: 16px;
background: black;
color: #25ff00;
font-weight: bold;
font-family: monospace;
line-height: 20px;
}
section {
position: fixed;
}
Hey setInterval uses the same event loop to run, for better performance use requestAnimationFrame which runs on a separate event loop leaving the current one open for other things, see the code below
function doString(){
// Do your string stuff
var rid = requestAnimationFrame(doString);
}
doString();
If you want to control the FPS see this post
Related
How to create a screensaver in javascript
I want to create a screensaver in JavaScript but I don't know how can I set the time between the images,. I have an Ajax call and I see if the time is, for example, 2s or 90s, but I don't know how to set that time between images, this is my code: var cont = 0; var time = 1000 setInterval(function() { console.log(tiempo); if(cont == imagenes.length){ return cont = 0; }else{ var imagen = imagenes[cont].imagen; $('#imgZona').attr('src', imagen); var time = imagenes[cont].tiempoVisible; finalTime = Number(time); } cont++; }, Number(finalTime )); but the time between images is always the same, 1000, how can I change it for the time that I receive in the Ajax call? Which is imagenes[cont].tiempoVisible
I cannot comment as I don't have enough reputation, but take a look at this fiddle https://jsfiddle.net/kidino/4mbpR/ var mousetimeout; var screensaver_active = false; var idletime = 5; function show_screensaver(){ $('#screensaver').fadeIn(); screensaver_active = true; screensaver_animation(); } function stop_screensaver(){ $('#screensaver').fadeOut(); screensaver_active = false; } function getRandomColor() { var letters = '0123456789ABCDEF'.split(''); var color = '#'; for (var i = 0; i < 6; i++ ) { color += letters[Math.round(Math.random() * 15)]; } return color; } $(document).mousemove(function(){ clearTimeout(mousetimeout); if (screensaver_active) { stop_screensaver(); } mousetimeout = setTimeout(function(){ show_screensaver(); }, 1000 * idletime); // 5 secs }); function screensaver_animation(){ if (screensaver_active) { $('#screensaver').animate( {backgroundColor: getRandomColor()}, 400, screensaver_animation); } } It will change background-color on idle mouse for 5 seconds, you can replace the code to change image, instead of background color.
Control set every timeout on each iteration. var cont = 0; var time = 1000 function next () { console.log(tiempo); if(cont == imagenes.length){ cont = 0; } var imagen = imagenes[cont].imagen; $('#imgZona').attr('src', imagen); cont++; setTimeout(next, Number(imagenes[cont].tiempoVisible)); } setTimeout(next, Number(initialTime)); Also I fixed a frindge condition.
Adding click event on a specific div with JavaScript
I have a snowfall effect in JavaScript, I want the create a click event on the 10th snowflake that falls down. I would also like to add a Class that says "clickable". We want to achieve this, so if user clicks the 10th snowflake only, then they will be redirected to a prize (we also want to add a Class so we can style this nicely via CSS). I must admit, my JavaScript knowledge isn't as strong as my jQuery, so could someone please help? Here is the codepen that I am using:- https://codepen.io/scottYg55/pen/GRRzOgO #snowflakeContainer { position: absolute; left: 0px; top: 0px; bottom: 0; right: 0; background-color: #2c3e50; } .snowflake { padding-left: 15px; font-family: Cambria, Georgia, serif; font-size: 14px; line-height: 24px; position: fixed; color: #FFFFFF; user-select: none; z-index: 1000; } <div id="snowflakeContainer"> <p class="snowflake">*</p> </div> JS // The star of every good animation var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; var transforms = ["transform", "msTransform", "webkitTransform", "mozTransform", "oTransform"]; var transformProperty = getSupportedPropertyName(transforms); // Array to store our Snowflake objects var snowflakes = []; // Global variables to store our browser's window size var browserWidth; var browserHeight; // Specify the number of snowflakes you want visible var numberOfSnowflakes = 50; // Flag to reset the position of the snowflakes var resetPosition = false; // // It all starts here... // function setup() { window.addEventListener("DOMContentLoaded", generateSnowflakes, false); window.addEventListener("resize", setResetFlag, false); } setup(); // // Vendor prefix management // function getSupportedPropertyName(properties) { for (var i = 0; i < properties.length; i++) { if (typeof document.body.style[properties[i]] != "undefined") { return properties[i]; } } return null; } // // Constructor for our Snowflake object // function Snowflake(element, radius, speed, xPos, yPos) { // set initial snowflake properties this.element = element; this.radius = radius; this.speed = speed; this.xPos = xPos; this.yPos = yPos; // declare variables used for snowflake's motion this.counter = 0; this.sign = Math.random() < 0.5 ? 1 : -1; // setting an initial opacity and size for our snowflake this.element.style.opacity = .1 + Math.random(); this.element.style.fontSize = 12 + Math.random() * 50 + "px"; } // // The function responsible for actually moving our snowflake // Snowflake.prototype.update = function () { // using some trigonometry to determine our x and y position this.counter += this.speed / 5000; this.xPos += this.sign * this.speed * Math.cos(this.counter) / 40; this.yPos += Math.sin(this.counter) / 40 + this.speed / 30; // setting our snowflake's position setTranslate3DTransform(this.element, Math.round(this.xPos), Math.round(this.yPos)); // if snowflake goes below the browser window, move it back to the top if (this.yPos > browserHeight) { this.yPos = -50; } } // // A performant way to set your snowflake's position // function setTranslate3DTransform(element, xPosition, yPosition) { var val = "translate3d(" + xPosition + "px, " + yPosition + "px" + ", 0)"; element.style[transformProperty] = val; } // // The function responsible for creating the snowflake // function generateSnowflakes() { // get our snowflake element from the DOM and store it var originalSnowflake = document.querySelector(".snowflake"); // access our snowflake element's parent container var snowflakeContainer = originalSnowflake.parentNode; // get our browser's size browserWidth = document.documentElement.clientWidth; browserHeight = document.documentElement.clientHeight; // create each individual snowflake for (var i = 0; i < numberOfSnowflakes; i++) { // clone our original snowflake and add it to snowflakeContainer var snowflakeCopy = originalSnowflake.cloneNode(true); snowflakeContainer.appendChild(snowflakeCopy); // set our snowflake's initial position and related properties var initialXPos = getPosition(50, browserWidth); var initialYPos = getPosition(50, browserHeight); var speed = 5+Math.random()*40; var radius = 4+Math.random()*10; // create our Snowflake object var snowflakeObject = new Snowflake(snowflakeCopy, radius, speed, initialXPos, initialYPos); snowflakes.push(snowflakeObject); } // remove the original snowflake because we no longer need it visible snowflakeContainer.removeChild(originalSnowflake); // call the moveSnowflakes function every 30 milliseconds moveSnowflakes(); } // // Responsible for moving each snowflake by calling its update function // function moveSnowflakes() { for (var i = 0; i < snowflakes.length; i++) { var snowflake = snowflakes[i]; snowflake.update(); } // Reset the position of all the snowflakes to a new value if (resetPosition) { browserWidth = document.documentElement.clientWidth; browserHeight = document.documentElement.clientHeight; for (var i = 0; i < snowflakes.length; i++) { var snowflake = snowflakes[i]; snowflake.xPos = getPosition(50, browserWidth); snowflake.yPos = getPosition(50, browserHeight); } resetPosition = false; } requestAnimationFrame(moveSnowflakes); } // // This function returns a number between (maximum - offset) and (maximum + offset) // function getPosition(offset, size) { return Math.round(-1*offset + Math.random() * (size+2*offset)); } // // Trigger a reset of all the snowflakes' positions // function setResetFlag(e) { resetPosition = true; } Thank you in advance for your help! Greatly appreciated
When generating the snowflakes, try to catch the 10th one and apply what you want ... function generateSnowflakes() { ... // create each individual snowflake for (var i = 0; i < numberOfSnowflakes; i++) { // clone our original snowflake and add it to snowflakeContainer var snowflakeCopy = originalSnowflake.cloneNode(true); snowflakeContainer.appendChild(snowflakeCopy); // <------------------------------------------> // catch the tenth snowflake and add your changes here if (i === 9) { snowflakeCopy.classList.add("clickable"); snowflakeCopy.addEventListener("click",function (e) { //the stuff you want to do //when the 10th snowflake is clicked // for example : window.location.replace("URL/OF/PRIZE"); }) } .... } ... ```
Are there different ways of controlling the font-weight locally or on an uploaded web page?
We have created a typewriter simulator which controls the font weight depending on how fast you type, it is controlled with some javascript, some jQuery and styled with css. Here's the link.https://www.dynamik.systems/typewriter/ Before we uploaded it to the site we coded everything with the program Brackets and the font is something we created with a much wider weight then the usual ones. But now, when everything is up and running it doesn't work like it did from when we coded it locally. No code is changed and the font-face is the same. We have tried it in Chrome, Safari and Firefox. This is how it looks locally loaded: https://imgur.com/yNxaX8B This is how it looks now: https://imgur.com/OpoYxpV This is with another font that shows that the code is working: https://imgur.com/HKPxMlm We only have tried different types of font styles (otf, ttf, wow etc.) because that's the only thing we can come up with. The jquery and js. (function($) { let i = 0; let lastTime = 0; const $output = $("#output"); const $content = $('#content'); $(window).on("keypress", function(event) { // Prevent backspace, enter and escape if(event.which === 13 || event.which === 8 || event.which === 27) { console.log('No output'); return; } // Keypress counter $("span2").text(i += 1 ); // Intervall for scrollbar setInterval(updateScroll,5000); // Timer for Key llklkLatency let currTime = new Date(); let ms = lastTime ? currTime - lastTime : 0; $output.text(ms + "ms"); lastTime = currTime; // Fontweight var fontWeight = ms > 900 ? 100 : 900 / ms * 100; // Typewriter var key = event.key; $content.append('<span style="font-weight:' + fontWeight + '">' + key + '</span>'); //Font-Weight $("span3").text(fontWeight); }); })( jQuery ); function updateScroll(){ var element = document.getElementById("content"); element.scrollTop = element.scrollHeight; } // Timer const output = document.querySelector('output'); let timeRead = false; let now = 0; let interval = null; function startTimer() { let elapsedMil = Date.now() - now; let mil = (elapsedMil).toFixed(0) % 100; let sec = Math.floor(elapsedMil/1000) % 60; let min = Math.floor(elapsedMil/60000) % 60; let hou = Math.floor(elapsedMil/3600000) % 24; mil = padTime(mil); sec = padTime(sec); min = padTime(min); hou = padTime(hou); function padTime(num) { if (num < 10) { num = "0" + num; } return num; } output.textContent = hou + ":" + min + ":" + sec + ":" + mil; } window.onload = function () { now = Date.now(); interval = window.setInterval(startTimer, 10); }; var timer = new Timer(); timer.start(); timer.addEventListener('secondsUpdated', function (e) { $('#basicUsage').html(timer.getTimeValues().toString()); }); function myFunction() { document.body.style.backgroundColor = "white"; document.getElementById("info").style.color = "black"; document.getElementById("content").style.color = "black"; } function myFunction2() { document.body.style.backgroundColor = "black"; document.getElementById("info").style.color = "white"; document.getElementById("content").style.color = "white"; } function myFunction3() { document.body.style.backgroundColor = "#FF3700"; document.getElementById("info").style.color = "black"; document.getElementById("content").style.color = "black"; } Beginning of the CSS to call for the font: #font-face { font-family: "dynamik"; src: url('https://www.dynamik.systems/wp-content/themes/fonts/DynamikGX.eot') format('opentype'), url('https://www.dynamik.systems/wp-content/themes/fonts/DynamikGX.ttf') format('truetype'), url('https://www.dynamik.systems/wp-content/themes/fonts/DynamikGX.woff') format('woff'); } We expect the font-weight to be as the way when we uploaded it locally.
Paperjs bounce animation
Theres some wierd behaviour when playing with Paperjs, i was trying to curve a line up with 7 points separately - which works fine once, but when trying to make the link overshoot and return to 3 different points (to create a bounce effect) doesn't seem to play ball. On the second if statement, the 'counter' variable doesnt seem to increase instead of decrease, '+ steps' instead of '- steps'. Maybe i'm not using if statements properly in this case, or paperjs has some strange behaviour? Heres the codepen for it in full, click above the blue line to trigger it off. . Following is one setInterval for one of the points of the segment. var seg6first = true; var seg6sec = false; var seg6thir = false; setInterval(function() { if (seg6first == true) { counter = counter - steps; if (counter >= 230) { path.segments[6].point.y = counter; path.smooth(); } else { seg6first = false; seg6sec = true; } } if (seg6sec == true) { counter = counter + steps; if (counter <= 260) { path.segments[6].point.y = counter; path.smooth();} else { seg6sec = false; seg6thir = true; } } if (seg6sec == true) { counter = counter - steps; if (counter >= 250) { path.segments[6].point.y = counter; path.smooth(); } else { seg6thir = false; } } }, mintiming); Thanks!
Rather than manually building your bounce effect, you can use an animation library like GSAP. It has a lot of features that will make your task easier (see easing documentation). Here is an example of what you are trying to do (click on the canvas to animate the line). html, body { margin: 0; overflow: hidden; height: 100%; } canvas[resize] { width: 100%; height: 100%; } <canvas id="canvas" resize></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.8/paper-full.min.js"></script> <script type="text/paperscript" canvas="canvas"> // user defined constants var SEGMENTS_COUNT = 6; var CURVE_HEIGHT = 80; var ANIMATION_DURATION = 2; // init path var path = new Path({ fillColor: 'orange', selected: true }); // add points for (var i = 0; i <= SEGMENTS_COUNT; i++) { path.add([view.bounds.width * i / SEGMENTS_COUNT, view.center.y]); } // on mouse down... function onMouseDown() { // ...animate points for (var i = 0, l = path.segments.length; i < l; i++) { // get a reference to the point var point = path.segments[i].point; // calculate offset using sine function to form a curve var offset = CURVE_HEIGHT * Math.sin(point.x * Math.PI / view.bounds.width); // register animation TweenLite.fromTo( // target point, // duration ANIMATION_DURATION, // initial value { y: view.center.y }, { // final value y: view.center.y - offset, // easing ease: Elastic.easeOut.config(1, 0.3), // on update... onUpdate: function() { // ...smooth the path path.smooth(); } } ); } } </script>
How to add elements fast but not instantly in javascript?
I would like to fill an element with dots in random order. I have managed to write all the functionality, but I am not satisfied with the execution speed. If I add all of the points using a while loop, the points just seem to appear all at the same time. Therefore I add points one by one using a function that I call recursively with a timeout. This, on the other hand, appears too slow. Is there any chance to run a sequence of actions slower than in a loop but faster than setTimeout() can? var dotCellSize; var initialOffset; var slotsHorizontally; var slotsVertically; var container; var redDots; var dots; var newDotElement = $('<div class="dot">'); function randomInteger(min,max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function addDots() { if (!dots.length) return; var dotIndex = randomInteger(0, dots.length - 1); var dot = dots[dotIndex]; dots.splice(dotIndex, 1); var column = dot % slotsHorizontally; var row = Math.floor(dot/slotsHorizontally); var position = { left: initialOffset + column*dotCellSize, top: initialOffset + row*dotCellSize }; var dotElement = newDotElement.clone().css(position); if (-1 != redDots.indexOf(dot)) dotElement.addClass('red'); dotElement.appendTo(container); setTimeout(function() { addDots(); }, 1); } function generateDots(dotContainer, cellSize, numberOfRedDots) { container = dotContainer; dotCellSize = cellSize; dots = []; redDots = []; container.find('div.dot').remove(); numberOfRedDots = typeof numberOfRedDots !== 'undefined' ? numberOfRedDots : 3; initialOffset = Math.floor(dotCellSize/2); slotsHorizontally = Math.ceil(container.width()/dotCellSize); slotsVertically = Math.ceil(container.height()/dotCellSize); var numberOfSlots = slotsHorizontally*slotsVertically; while (dots.length < numberOfSlots) dots.push(dots.length); while (redDots.length < numberOfRedDots) { var newRedDot = randomInteger(0, numberOfSlots - 1); if (-1 == redDots.indexOf(newRedDot)) redDots.push(newRedDot); } addDots(); } generateDots($('.dot-container'), 18, 15); .dot { width: 4px; height: 4px; border-radius: 50%; background-color: #C0E3EA; position: absolute; z-index: 1; } .dot.red { background-color: #EF3D48; } .dot-container { width: 420px; height: 280px; background-color: #333; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="dot-container"></div>
Unfortunately, not really, this is because of how the browser engine decides to repaint the screen. Without the timeout, the browser engine recognizes it's going to do a bunch of updates (adding the dots to the DOM). Because repainting the screen is expensive, it waits to do as much as possible at one time, and, in your case, all of the dots show up at once. With the timeout added, each call to your function gets "deferred" for future execution. This may or may not happen "right away" and is non-trivial to explain in detail so I would recommend watching this video https://www.youtube.com/watch?v=8aGhZQkoFbQ which explains the JS event loop or read some articles on browser reflow: Minimizing browser reflow What is Layout Thrashing? Without changing much of what you've already done, one solution is to batch a few of the dots to be drawn together. I've added a for loop to your function which will make five dots get drawn together. Adjust this to 10, 20, or higher and you'll see the dots get painted even faster. I hope there is a number that you'll find suitable. I understand you may want to just speed up the drawing of every dot individually, but bear in mind that screens have refresh rates, so the faster you want the routine to finish the more they will appear in batches any way. var dotCellSize; var initialOffset; var slotsHorizontally; var slotsVertically; var container; var redDots; var dots; var newDotElement = $('<div class="dot">'); function randomInteger(min,max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function addDots() { if (!dots.length) return; for (let i = 0; i < 5; i++) { var dotIndex = randomInteger(0, dots.length - 1); var dot = dots[dotIndex]; dots.splice(dotIndex, 1); var column = dot % slotsHorizontally; var row = Math.floor(dot/slotsHorizontally); var position = { left: initialOffset + column*dotCellSize, top: initialOffset + row*dotCellSize }; var dotElement = newDotElement.clone().css(position); if (-1 != redDots.indexOf(dot)) dotElement.addClass('red'); dotElement.appendTo(container); } setTimeout(function() { addDots(); }, 1); } function generateDots(dotContainer, cellSize, numberOfRedDots) { container = dotContainer; dotCellSize = cellSize; dots = []; redDots = []; container.find('div.dot').remove(); numberOfRedDots = typeof numberOfRedDots !== 'undefined' ? numberOfRedDots : 3; initialOffset = Math.floor(dotCellSize/2); slotsHorizontally = Math.ceil(container.width()/dotCellSize); slotsVertically = Math.ceil(container.height()/dotCellSize); var numberOfSlots = slotsHorizontally*slotsVertically; while (dots.length < numberOfSlots) dots.push(dots.length); while (redDots.length < numberOfRedDots) { var newRedDot = randomInteger(0, numberOfSlots - 1); if (-1 == redDots.indexOf(newRedDot)) redDots.push(newRedDot); } addDots(); } generateDots($('.dot-container'), 18, 15); .dot { width: 4px; height: 4px; border-radius: 50%; background-color: #C0E3EA; position: absolute; z-index: 1; } .dot.red { background-color: #EF3D48; } .dot-container { width: 420px; height: 280px; background-color: #333; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div class="dot-container"></div>
Quickly profiling on my i7 3770k revealed that newDotElement.clone().css(position) took about .1 seconds. If you are running at 30 FPS, your frame time is .03 seconds. So you can see that Jquery clone is somewhat of a bottleneck. However, your initial approach of drawing all the dots at once is sound, if you flag their styles to be "hidden". Then, when all the dots are added to the DOM, but are not visible, retrieve a list of their nodes (forgive the vanilla JS): Array.from(document.getElementsByClassName("dot-container")[0].childNodes); Now you can iterate over them and simply change their visibility style from "hidden" to "visible". As skyline3000 points out, the limit with setTimeout (or even requestAnimationFrame) is in the browser, and looping and setting one dot per iteration will take a little over 1 frame, which is actually a little slow. So you can write yourself a little abraction which per call will set a certain number of elements' visibility styles to "visible". By adjusting the quantity of dots you make visible per call, you will speed up or slow down the animation. function showDots() { var list = Array.from(document.getElementsByClassName("dot-container")[0].childNodes); function draw(q) { var e; for (var i = 0; i < q; i++) { if (list.length == 0) { return; } e = list.shift(); e.style.visibility = "visible"; } } function callback() { if (list.length == 0) { return; } draw(4); setTimeout(callback); } callback(); }