Most performant way to do document wide PNG animation - javascript
I've got a GIF animation that I use thoughout my site as a saving/loading icon:
Because of edges on different background colors, I'd like to change it to a PNG animation.
When I want to show a loader at this moment I only have to make sure the following span is visible:
<span class="loader"></span>
There are several ways how this span be inserted in the document: through knockout visible binding, through JS, only by stylesheets etc.
Problem
I don't want my animation code to be aware of how this span ended up visible on the document, I just want him to animate it.
Of course scanning the whole document every frame (16 fps) for potential new spans with the 'loader' class, just to know which position properties need to be animated is not quite performant.
So what would be a good performant way to do a document wide png animation?
Note that I do need to support IE8 :(
Using a sprite in combination with CSS background-position comes immediately to mind:
(yes I know it's a pretty shitty one, but it'll do the job).
You mentioned Knockout, and as its aim is to separate logic from presentation, I'll use that. However, there's no way to completely separate it that I know of that doesn't come with a performance cost (cf your comment). Typically in Knockout bindingHandlers are used to do DOM manip independently of your viewModel.
Haven't tested, but should normally work on IE8. Run the snippet below for a demo
ko.bindingHandlers.loadIndicator = {
update: function(element, valueAccessor) {
var val = ko.unwrap(valueAccessor());
if (val == true) {
var intv = setInterval(function() {
var bgX = parseInt(element.style.backgroundPosition.split(' ')[0].replace('px',''));
if (bgX > -48) // 4 frames of 14px
element.style.backgroundPosition = (bgX - 14) + 'px';
else
element.style.backgroundPosition = '0px';
}, 150);
element.style.display = 'block';
} else {
element.style.display = 'none';
clearInterval(intv);
}
}
};
var app = { loading: ko.observable(true) };
ko.applyBindings(app);
// simulate 'loaded' after 3 secs
setTimeout(function() { app.loading(false); }, 3000);
.loader {
display: block;
width: 14px;
height: 14px;
background-image: url(http://i.imgur.com/1OZACw8.png);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<span class="loader" data-bind="loadIndicator: loading"></span>
If you think a custom binding is not worth it, you can use the visible binding in combination with a transparent animated GIF (yes, it is possible). See for example: http://blog.ciuly.com/general/internet/making-animated-gif-transparent-with-gimp/.
Any way you choose, you'll have to keep track of whether something has finished loading/ saving, for example in a KO observable property.
Prefer not using Knockout? You could do the same in vanilla JS/ jQuery. However, because you have to track the status from somewhere, you have to either interval-check DOM attributes (which achieves the same as Knockout does automatically on observables) or choose a variant of the approach below where you call an init/ stop function to hide/display the loader.
function loader(container) {
var elem = document.createElement('span');
elem.className = 'loader';
container.appendChild(elem);
var intv = setInterval(function() {
var bgX = parseInt(elem.style.backgroundPosition.split(' ')[0].replace('px',''));
if (bgX > -48) // 4 frames of 14px
elem.style.backgroundPosition = (bgX - 14) + 'px';
else
elem.style.backgroundPosition = '0px';
}, 150);
this.stop = function() {
clearInterval(intv);
container.removeChild(elem);
};
}
var x = new loader(document.body);
setTimeout(function() { x.stop() }, 10000);
Performance-wise I believe the animated transparent GIF with visible binding and the vanilla init/stop method are the 2 best candidates.
Related
Is it possible to add a transition (like in CSS) in Javascript?
I have a Javascript code that shows a tooltip when hovering over an HTML element. Now I want to give this element a latency of about 6 milliseconds. In CSS it is very easy with the transition command. However, I did not find a transition style command in Javascript. Is there a solution or do I have to change to another programming language? Javascript code: var bghtooltipin = document.getElementById('bgh-tooltipin1'); var bghtooltipout = document.getElementById('bgh-tooltipout1'); bghtooltipin.addEventListener('mouseover', bghtooltipinmouseOver); bghtooltipin.addEventListener('mouseout', bghtooltipoutmouseOut); function bghtooltipinmouseOver() { bghtooltipout.innerHTML = 'Go to Login'; bghtooltipout.style.color = "white"; bghtooltipout.style.top = "0"; } function bghtooltipoutmouseOut() { bghtooltipout.innerHTML = ' '; bghtooltipout.style.top = "-99999px" }
You can use something like this: bghtooltipout.style.transition = "all 6s";
something like this it works is Vanila JS bghtooltipout.style.transition = "all 2s";
There are 2 ways to interpret "latency". I will show you how to perform both implementations. Delay. 6ms would pass, and then the transition would play. In JavaScript, this is done as the following: setTimeout(function() { // Code here }, delay_in_ms); Duration. If you want your animation to last for 6ms, then you would do something as follows: const element = document.querySelector("#testthing"); element.addEventListener("mouseover", function(){ this.style.opacity = "0"; this.style.transition = "opacity 0.6s"; }); element.addEventListener("mouseout", function(){ this.style.opacity = "1"; this.style.transition = "opacity 0.6s"; }); #testthing { width: 100px; height: 100px; background: red; } <div id="testthing"></div> PLEASE NOTE: In this example, the transition actually lasts for 600 milliseconds, not 6. This is because 6ms is just too quick to see. It just appears as an instant change.
Changing background Image depending on level in game javascript?
So I'm building a water pipe base game which can be seen here http://www.mckenziedave.co.uk/client_files/gabi_pipes/ I thought id post it rather than explain it. Im using HTML5 and JS script but I have hit a little problem, I wish to change the background depending on what level the user is on. Would this be best done by CSS or could I implement it into the java script? Instead of posting all the scripts I have just posted the level selector and level creator (the game board works on a basis of 0/1). $(document).ready(function(){ PipeGame.configure({ cols: 4, rows: 6, startX:0, StartY:0, lastX:3, lastY:5, godMode: true, autoStart: null }); var board =[[[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]], [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]]; PipeGame.setGameBoard(board); var slider = new Slider($(".options")) }); var Slider = function(el){ this.el = el; this.dragging = false; this.startx = this.el.offset().left; this.el.on("touchstart",this.startDrag.bind(this)); this.el.on("touchmove",this.drag.bind(this)); this.el.on("touchend",this.stopDrag.bind(this)); } Slider.prototype.startDrag = function(e){ this.startx = e.originalEvent.touches[0].pageX; this.dragging = true; } Slider.prototype.stopDrag = function(e){ this.dragging = false; } Slider.prototype.drag = function(e){ var pos = Math.round($(".plumbing-creator").position().left - (this.startx - e.originalEvent.touches[0].pageX) ); if(this.dragging){ this.el.css({left: pos +"px"}) } } var levels = { level1: [[["0","1","0","1"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","1","1"],["0","1","1","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","0","0"],["0","0","1","1"],["1","1","0","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","0","0"],["0","0","0","0"],["0","0","1","1"],["1","0","1","0"],["1","0","1","0"],["1","1","0","0"]]], level2: [[["0","1","1","0"],["0","1","1","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","0","0"],["0","1","1","0"],["0","1","1","0"],["0","0","0","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","0","0"],["0","0","0","0"],["0","1","1","0"],["0","1","1","0"],["0","0","0","0"],["0","0","0","0"]],[["0","0","0","0"],["0","0","0","0"],["0","0","0","0"],["0","1","1","0"],["0","1","0","1"],["0","1","1","0"]]], level3: [[["0","1","0","1"],["0","1","1","0"],["0","0","1","1"],["1","1","0","0"],["0","1","1","0"],["1","1","0","0"]],[["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"]],[["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"],["0","1","0","1"]],[["0","0","1","1"],["1","0","0","1"],["0","0","1","1"],["0","1","0","1"],["1","0","0","1"],["0","1","0","1"]]], } Anyone know of an easy way to change the background depending on the level?? Thank you :)
You should use CSS. For each level, you can create a different CSS class. Example: .level1 { background-color: red; } .level2 { background-color: blue; }
I can think of a couple ways off the top of my head depending on the rest of your code. 1) When a level changes, set a class on the body. JS document.getElementsByTagName('body')[0].className = 'level-whatever'; CSS body.level-whatever { background: do-what-you-want } 2) Just change the background with JS. var body = document.getElementsByTagName('body')[0]; body.style.backgroundImage = 'url("whatever-url")';
I notice that your main.js, which has the following configuration: PipeGame.configure({ cols: 4, rows: 6, startX:0, StartY:0, lastX:3, lastY:5, godMode: true, autoStart: null }); Is called everytime you visit a different arcade.html#* page. You could potentially add another configuration setting: background: level*, And change something based on that. To be honest, this is a pretty broad question with a lot of different ways to accomplish the task at hand. Applying a CSS rules based on your level (like so): var currentLevel = parseInt(window.location.hash.substring(1)) || 1; if(currentLevel == 1){ $("body").addClass("last-level"); $("body").style(...); } // Use as a Case Statement or have an array of level backgrounds, etc Seems to be the consensus here.
onmouseout and onmouseover
I am working on homework that involves working with javascript. Part of my homework assignment is to use the event handlers onmouseout and onmouseouver. What is supposed to happen when the user hovers over a specific div element, the font size grows by 25%, and when the user mouses out of the div element, the font size goes back to normal. My question is, is it possible to incorporate both an onmouseover function and an onmouseout function into one function? Somehow that is what my teacher wants us to do. I have this started so far. function FontSize(x) { x.style.fonstSize = large; } I'm also thinking this isnt the correct code to make the font 25% larger, but I'm not sure how to really incorporate an onmouseout in this function.
As a teacher myself, I am 99% sure that by "one function" the instructor means one general-purpose function to change the font size, not one function which uses conditional statements to work backwards and figure out whether it should be doing onmouseout or onmouseover. Your script should contain: function resize(elem, percent) { elem.style.fontSize = percent; } Your HTML should contain: <div onmouseover="resize(this, '125%')" onmouseout="resize(this, '100%')" Text within div.. </div> Note: Situations such as here, are exactly why JavaScript has the keyword "this"--to save us from needing to use complicated document.getElementById() statements.
You can use "%" property for controlling font-size as described here with the following code. document.getElementById("div1").onmouseover = function() { document.getElementById("div1").style.fontSize = "125%" }; document.getElementById("div1").onmouseout = function() { document.getElementById("div1").style.fontSize = "100%"; }; Here is the working jsfiddle : http://jsfiddle.net/LxhdU/
Yes you can. Call the same function on both events, and pass a parameter to indicate whether the fontsize should increase or decrease. ChangeFontSize = function(element, shouldIncreaseFontsize) { var small=14; var large = small * 1.25; if(shouldIncreaseFontsize) { element.style.fontSize = large + "px"; } else { element.style.fontSize = small + "px"; } } http://jsfiddle.net/TMHbW/1/
I'd do something simple like the following. The large and small values can be whatever you need them to be for the font size to work or they can be variables you've defined in prior code. Demo: http://jsfiddle.net/lucuma/EAbYn/ function doHover(e) { if (e.type=='mouseover') { this.style.fontSize = "large"; } else { this.style.fontSize = "small"; } } var el = document.getElementById('myelement') el.onmouseout =doHover; el.onmouseover=doHover;
It is possible you do not need to call both the events on the element explicitly instead extension you create will do that.Extend the Element's prototype. Jquery also does similar to this. Ref Prototype See Fiddle:- http://jsfiddle.net/4fs7V/ Element.prototype.hover= function( fnOver, fnOut ) { this.onmouseover=fnOver; this.onmouseout=fnOut || fnOver; return this; }; document.getElementById('test').hover(function(){ //do your mouseover stuff }, function(){ //do your mouseout stuff }); Update Same can be achieved with just one function too:- Hover me .largeFont { font-size:125%; } Element.prototype.hover = function (fnOver, fnOut) { this.onmouseover = fnOver; this.onmouseout = fnOut || fnOver; return this; }; document.getElementById('test').hover(changeMe); function changeMe() { if(this.hasAttribute('class')) { this.removeAttribute('class'); } else { this.setAttribute('class', 'largeFont'); } }
Can I optimise this js Prototype gallery to animate faster in IE?
I'm using a js image gallery on a magento site. Because Magento uses prototype, I've used prototype for this gallery; it's a simple application and I thought it unnecessary to load jQuery library as well just for this one element. If you have a look at http://web74.justhost.com/~persia28/ in IE8 or less, the transitions between gallery slides is so slow, to the point where the text from one slide remains visible for a short time when the next slide is in place. I know IE is rubbish with js, but I thought the extent of the slowness here is extreme, even for IE. I don't want to load jQuery library just for this one gallery, Magento is enough of a tank as it is; so if it came to that I might opt for just putting the text in the images, not in HTML. Anyway, would be great to hear your wisdom. Many thanks, and here is the js code for the gallery. var i = 0; // The array of div names which will hold the images. var image_slide = new Array('image-1', 'image-2', 'image-3'); // The number of images in the array. var NumOfImages = image_slide.length; // The time to wait before moving to the next image. Set to 3 seconds by default. var wait = 4000; // The Fade Function function SwapImage(x,y) { $(image_slide[x]).appear({ duration: 1.5 }); $(image_slide[y]).fade({duration: 1.5}); } // the onload event handler that starts the fading. function StartSlideShow() { play = setInterval('Play()',wait); $('PlayButton').hide(); $('PauseButton').appear({ duration: 0}); } function Play() { var imageShow, imageHide; imageShow = i+1; imageHide = i; if (imageShow == NumOfImages) { SwapImage(0,imageHide); i = 0; } else { SwapImage(imageShow,imageHide); i++; } } function Stop () { clearInterval(play); $('PlayButton').appear({ duration: 0}); $('PauseButton').hide(); } function GoNext() { clearInterval(play); $('PlayButton').appear({ duration: 0}); $('PauseButton').hide(); var imageShow, imageHide; imageShow = i+1; imageHide = i; if (imageShow == NumOfImages) { SwapImage(0,imageHide); i = 0; } else { SwapImage(imageShow,imageHide); i++; } } function GoPrevious() { clearInterval(play); $('PlayButton').appear({ duration: 0}); $('PauseButton').hide(); var imageShow, imageHide; imageShow = i-1; imageHide = i; if (i == 0) { SwapImage(NumOfImages-1,imageHide); i = NumOfImages-1; } else { SwapImage(imageShow,imageHide); i--; } }
I looked at the site and it doesn't seem slow, it takes the same time to run. It looks as though the text is not changing in opacity until the end of the animation then is just being hidden. When I look with IE7 it works normally which is a clue, IE8 has a different way of making transparencies. Magento still ships with Prototype 1.6.0 when I know that Prototype 1.6.1 fixes several IE8 bugs and Prototype 1.7 fixes some IE9 bugs too. You could try upgrading Prototype and Scriptaculous in the js/prototype/ and js/scriptaculous/ directories. I don't know if that exact problem is included which is why you should make a backup before overwriting files, if this doesn't work you will have something to rollback to.
How to improve image cross-fade performance?
I want to be able to do a cross fade transition on large images whose width is set to 100% of the screen. I have a working example of what I want to accomplish. However, when I test it out on various browsers and various computers I don't get a buttery-smooth transition everywhere. See demo on jsFiddle: http://jsfiddle.net/vrD2C/ See on Amazon S3: http://imagefader.s3.amazonaws.com/index.htm I want to know how to improve the performance. Here's the function that actually does the image swap: function swapImage(oldImg, newImg) { newImg.css({ "display": "block", "z-index": 2, "opacity": 0 }) .removeClass("shadow") .animate({ "opacity": 1 }, 500, function () { if (oldImg) { oldImg.hide(); } newImg.addClass("shadow").css("z-index", 1); }); } Is using jQuery animate() to change the opacity a bad way to go?
You might want to look into CSS3 Transitions, as the browser might be able to optimize that better than Javascript directly setting the attributes in a loop. This seems to be a pretty good start for it: http://robertnyman.com/2010/04/27/using-css3-transitions-to-create-rich-effects/
I'm not sure if this will help optimize your performance as I am currently using IE9 on an amped up machine and even if I put the browser into IE7 or 8 document mode, the JavaScript doesn't falter with your current code. However, you might consider making the following optimizations to the code. Unclutter the contents of the main photo stage by placing all your photos in a hidden container you could give an id of "queue" or something similar, making the DOM do the work of storing and ordering the images you are not currently displaying for you. This will also leave the browser only working with two visible images at any given time, giving it less to consider as far as stacking context, positioning, and so on. Rewrite the code to use an event trigger and bind the fade-in handling to the event, calling the first image in the queue's event once the current transition is complete. I find this method is more well-behaved for cycling animation than some timeout-managed scripts. An example of how to do this follows: // Bind a custom event to each image called "transition" $("#queue img").bind("transition", function() { $(this) // Hide the image .hide() // Move it to the visible stage .appendTo("#photos") // Delay the upcoming animation by the desired value .delay(2500) // Slowly fade the image in .fadeIn("slow", function() { // Animation callback $(this) // Add a shadow class to this image .addClass("shadow") // Select the replaced image .siblings("img") // Remove its shadow class .removeClass("shadow") // Move it to the back of the image queue container .appendTo("#queue"); // Trigger the transition event on the next image in the queue $("#queue img:first").trigger("transition"); }); }).first().addClass("shadow").trigger("transition"); // Fire the initial event Try this working demo in your problem browsers and let me know if the performance is still poor.
I had the same problem too. I just preloaded my images and the transitions became smooth again.
The point is that IE is not W3C compliant, but +1 with ctcherry as using css is the most efficient way for smooth transitions. Then there are the javascript coded solutions, either using js straight (but need some efforts are needed to comply with W3C Vs browsers), or using libs like JQuery or Mootools. Here is a good javascript coded example (See demo online) compliant to your needs : var Fondu = function(classe_img){ this.classe_img = classe_img; this.courant = 0; this.coeff = 100; this.collection = this.getImages(); this.collection[0].style.zIndex = 100; this.total = this.collection.length - 1; this.encours = false; } Fondu.prototype.getImages = function(){ var tmp = []; if(document.getElementsByClassName){ tmp = document.getElementsByClassName(this.classe_img); } else{ var i=0; while(document.getElementsByTagName('*')[i]){ if(document.getElementsByTagName('*')[i].className.indexOf(this.classe_img) > -1){ tmp.push(document.getElementsByTagName('*')[i]); } i++; } } var j=tmp.length; while(j--){ if(tmp[j].filters){ tmp[j].style.width = tmp[j].style.width || tmp[j].offsetWidth+'px'; tmp[j].style.filter = 'alpha(opacity=100)'; tmp[j].opaque = tmp[j].filters[0]; this.coeff = 1; } else{ tmp[j].opaque = tmp[j].style; } } return tmp; } Fondu.prototype.change = function(sens){ if(this.encours){ return false; } var prevObj = this.collection[this.courant]; this.encours = true; if(sens){ this.courant++; if(this.courant>this.total){ this.courant = 0; } } else{ this.courant--; if(this.courant<0){ this.courant = this.total; } } var nextObj = this.collection[this.courant]; nextObj.style.zIndex = 50; var tmpOp = 100; var that = this; var timer = setInterval(function(){ if(tmpOp<0){ clearInterval(timer); timer = null; prevObj.opaque.opacity = 0; nextObj.style.zIndex = 100; prevObj.style.zIndex = 0; prevObj.opaque.opacity = 100 / that.coeff; that.encours = false; } else{ prevObj.opaque.opacity = tmpOp / that.coeff; tmpOp -= 5; } }, 25); }