CSS3 webkitAnimationEnd event ordering - css

I'm new to Stack Overflow and also relatively new to HTML5 programming. I'm writing something (for Safari, primarily) where the logic is driven by the events which get fired out when webkit animations complete. If I start a number of animations of the same length simultaneously, I need some idea of the order I can expect their completion events to fire. Example:
<!DOCTYPE html>
<html>
<head>
<style>
#-webkit-keyframes slideRight {
from { left: 0; }
to { left: 100px; }
}
</style>
</head>
<body>
<script type="text/javascript">
var square = function(yPos, color){
var myDiv = document.createElement("div");
myDiv.style.width = "20px";
myDiv.style.height = "20px";
myDiv.style.top = yPos + "px";
myDiv.style.backgroundColor = color;
myDiv.style.position = "absolute";
document.body.appendChild(myDiv);
var squareInterface = {
onAnimEnd: function(event){
console.log(myDiv.style.backgroundColor + " square finished animating");
},
startAnim: function(){
myDiv.style.webkitAnimationName = "slideRight";
myDiv.style.webkitAnimationDuration = "2s";
myDiv.addEventListener('webkitAnimationEnd', this.onAnimEnd);
}
}
return squareInterface;
}
var myRedFoo = square(0, "red");
var myBlueFoo = square(30, "blue");
myRedFoo.startAnim();
myBlueFoo.startAnim();
</script>
</body>
</html>
So, I'm creating a red square and a blue square in JavaScript, and (in Safari and Chrome) kicking off animations to move them to the right, and to print to the console when they're done. The blue square is always the first to say that it's finished animating. From playing around it seems to have nothing to do with the order in which the animations were started, or the positions of the squares, but the order in which they're created. "Simultaneous" event callbacks seem to occur on the most recently created element first, followed by the older elements.
My question is can I rely on this behaviour? Is it guaranteed in any standards, or is it likely to change depending on the browser, or the phase of the moon? If the event order can't be guaranteed, what strategies would you recommend for coping with that?

I can say that this is probably system dependent. I'm using OSX Lion, and in both Chrome and Safari the "red" event is logged before the "blue" one.
If you want to hack it out so that you can be more confident in the timings, do something as such:
function startRedFoo(){ myRedFoo.startAnim() };
myBlueFoo.startAnim();
setTimeout(startRedFoo, 10); //Ten is as small as you can go.
You would think that you would be able to set the timeout function to myRedFoo.startAnim but that prevents the messages from being logged.
I can still imagine potential timing issues with this though, so it's not fool proof.

Related

A-Frame. Zoom on wheel scroll

I've come through the official docs but wasn't able to locate information about how possibility of zooming in/out panorama images, is it supported in the A-Frame or maybe there is a workaround to read about implementing some of three.js on top of it?
This might be a cleaner way in 2018.
I limited the zoom of the Aframe camera 1-5 so it doesn't get too messy.I just tested this and its working greatly.Hope it helps others.
window.addEventListener("mousewheel", event => {
const delta = Math.sign(event.wheelDelta);
//getting the mouse wheel change (120 or -120 and normalizing it to 1 or -1)
var mycam=document.getElementById('cam').getAttribute('camera');
var finalZoom=document.getElementById('cam').getAttribute('camera').zoom+delta;
//limiting the zoom so it doesnt zoom too much in or out
if(finalZoom<1)
finalZoom=1;
if(finalZoom>5)
finalZoom=5;
mycam.zoom=finalZoom;
//setting the camera element
document.getElementById('cam').setAttribute('camera',mycam);
});
You could either:
Scale an <a-sphere> up or down when detecting the mouse wheel event
zoom in or out the camera, like documented here
This article might be helpful, as it covers using the mousewheel event on multiple browsers.
I think scaling may screw up Your setup, or be a resource waste, so I'd go with 2.
Sandy's answer helped me. I want to contribute an answer which shows the full code and enables smoother zooming (increments of 0.1):
<script>
window.addEventListener("wheel", (event) => {
// small increments for smoother zooming
const delta = event.wheelDelta / 120 / 10;
var mycam = document.getElementById("cam").getAttribute("camera");
var finalZoom =
document.getElementById("cam").getAttribute("camera").zoom + delta;
// limiting the zoom
if (finalZoom < 0.5) finalZoom = 0.5;
if (finalZoom > 2) finalZoom = 2;
mycam.zoom = finalZoom;
document.getElementById("cam").setAttribute("camera", mycam);
});
</script>
<a-scene>
<a-entity
id="cam"
camera="zoom: 1"
look-controls="reverseMouseDrag: true"
></a-entity>
<!-- my pano image stuff -->
<a-assets>
<img id="skyTexture" crossorigin="anonymous" />
</a-assets>
<a-sky src="#skyTexture"></a-sky>
</a-scene>
This is what I put together to do it. Check the initial vrZoom variable.
For me, what I struggled the most, was to understand the way you set a parameter that's inside a component. You have to call it like this: element.setAttribute('componentName', 'parameterName', 'value') and in my case cam.setAttribute('camera', 'zoom', vrZoom)
Here's my script all together. It would be possible to create a component with this, such as look-controls.
var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel";
if (document.attachEvent)
document.attachEvent("on"+mousewheelevt, function(e){scroller(e)});
else if (document.addEventListener)
document.addEventListener(mousewheelevt, function(e){scroller(e)},false);
var vrZoom = 4; // My initial zoom after animation
var cam = document.querySelector('#mainCam');
function scroller(evt)
{
//Guess the delta.
var delta = 0;
if (!evt) evt = window.event;
if (evt.wheelDelta) {
delta = evt.wheelDelta/120;
} else if (evt.detail) {
delta = -evt.detail/3;
}
if (evt.preventDefault) evt.preventDefault();
evt.returnValue = false;
//Actual Zooming.
vrZoom += delta * 0.1
vrZoom = Math.min(Math.max(vrZoom, 1), 8); // clamp between 1 and 8
cam.setAttribute('camera', 'zoom', vrZoom)
}
I struggled quite a bit with getting this to work for an embedded a-frame, especially because the scene would become skewed upon dynamically adjusting the camera's zoom setting. This is a bug with a-frame. Here are the two ways I found to reset the scene upon setting the zoom level.
AFRAME.scenes[0].resize();
Or ...
let scene = document.querySelector('a-scene');
scene.camera.aspect = scene.clientWidth / scene.clientHeight;
scene.camera.updateProjectionMatrix();

CSS animation performance

I have a small hobby project in which I try to build a matrix rain: .
See demo here. Or this JSFiddle
My question is: how can I make this more efficient, as I can see it gets slow when I add a lot of columns.
I have implemented it as rendering a lot of absolute positioned divs that are animated.
Here is my CSS:
div
{
position:absolute;
width:1em;
display:inline-block;
color: black;
animation-name: example;
animation-duration: 2s;
text-shadow: none;
}
#keyframes example
{
0% {color: white; text-shadow: -1px 1px 8px white;}
15% {color: #5f5 ; text-shadow: -1px 1px 8px #5f5 ;}
100% {color: black; text-shadow: none;}
}
In javascript I set some custom styling for each div, where I vary some settings, like font-size, animation speed etc.
Main part of the JS:
var textStrip = ['诶', '比', '西', '迪', '伊', '吉', '艾', '杰', '开', '哦', '屁', '提', '维'];
var matrixcol = function()
{
var top = Math.floor(Math.random() * $(window).height() * 0.5);
var size = 10 + Math.floor(Math.random()*10);
var col = Math.floor(Math.random() * $(window).width() - size);
var ms = 500 + Math.floor(Math.random()*1500);
var timer;
var aap = function()
{
var randomNumber = Math.floor(Math.random()*textStrip.length);
var newelem = $("<div style='font-size:"+ size+ "px;top:"+top+"px; left:"+col+"px;animation-duration:"+ 2*ms + "ms'>" + textStrip[randomNumber] + "</div>" );
$('body').append(newelem);
top+=size;
setTimeout( function() {newelem.remove();}, (1.6*ms)-(ms/40));
if (top>$(window).height()-size)
{
size = 10 + Math.floor(Math.random()*10);
top=0; Math.floor(Math.random() * $(window).height() * 0.5);
col = Math.floor(Math.random() * $(window).width() -size);
ms = 500 + Math.floor(Math.random()*1500);
clearInterval(timer);
timer = setInterval(aap, ms/40);
}
}
timer = setInterval(aap, ms/40);
}
$( document ).ready(function() {
var i;
for (i = 0; i < 25; i++) {
matrixcol();
}
I have tried to use the chrome profiling, that shows my a warning:
Long frame times are an indication of jank and poor rendering
performance.
The link that is provided gives some insight; however, as far a I can see I don't have much layouting going on.
tl;dr
It is slow. What would be a good performance optimizations?
After several try, I think your best solution is looking to canvas, if the exact animation is desired.
The ending result I get is here. Not as exact as yours but get a 50+ fps.
For every modification I have added comment, please check it out.
Cache
The easiest thing you can do is cache $(window).height(). It is usually a stable number, no need to re-query it. And resize handler can be added to adapt viewport change. Cache window size changes my fps from 9~10 to 12~15. Not big, but a low-hanging fruit.
Expensive Style
The next thing you need to do is remove text-shadow, it is a very expensive style, given the node number in your case. (Why? It requires CPU paints shadow and GPU cannot help here. read more here, and html5rocks).
If you are interested in Chromium implementation, text-shadow is done in TextPainter.cpp, painted by GraphicContext, which is done primarily by CPU. And animating text-shadow is a performance nightmare. Change this boost fps to 20+.
DOM Access
The last thing is DOM access, every frame update requires a dom insertion and, correspondingly, a dom removal by yet another timer. This is painful. I try to reduce DOM removal, so I added a container for each column. And adding container does add DOM complexity, I have to wait for the animation end to update the container. After all, it saves many dom manipulations, timers and closures. Furthermore I updated setTimeout to requestAnimationFrame so that browser can orchestra DOM access better.
Combining the above three, I got a 50+ fps, not as smooth as 60fps. Maybe I can further optimize it by reducing DOM insertion, where all characters in a column is inserted once, and for each character the animation-delay is at interval.
Looking on Canvas
Still, your animation is quite harsh job for DOM based implementation. Every column is updated, and text size varies frequently. If you really want the original matrix effect, try canvas out.

How to flow a working clock into text?

I am trying to place a live clock into a body of text. I need it to flow as if it were just part of the text, but still be live to the local device. Playing around in Adobe Muse I have been able to get a clock into the text, but it segregates itself to its own line rather than flowing like part of the paragraph.
Following is the code Muse produced. I assume I need to make a change to either actAsInlineDiv normal_text, or actAsDiv excludeFromNormalFlow, or both, but how?
<p id="u3202-10"><span class="Character-Style">You look at the clock on this device and it reads </span><span class="Character-Style"><span class="actAsInlineDiv normal_text" id="u13390"><!-- content --><span class="actAsDiv excludeFromNormalFlow" id="u13388"><!-- custom html --><html>
<head>
<script type="text/javascript">
function startTime()
{
var today=new Date();
var h=today.getHours();
var m=today.getMinutes();
// add a zero in front of numbers<10
m=checkTime(m);
document.getElementById('txt').innerHTML=h+":"+m;
t=setTimeout('startTime()',500);
}
function checkTime(i)
{
if (i<10)
{
i="0" + i;
}
return i;
}
</script>
</head>
<body onload="startTime()">
<div id="txt"></div>
</body>
</html>
</span></span></span><span class="Character-Style">As a result you believe that this is the time. As it happens this is the time but unknown to you your device's clock has stopped functioning and is stuck. Does your true belief that this is the time count as knowledge?</span></p>
I don't know about Muse, but if all you want is a clock of the current time running inline with some text you could do this:
window.onload = displayTime;
function displayTime() {
var element = document.getElementById("clock");
var now = new Date();
var options = {hour: '2-digit', minute:'2-digit'};
element.innerHTML = now.toLocaleTimeString(navigator.language, options);
setTimeout(displayTime, 1000);
}
The current time is <span id="clock"></span> and it's inline with text.
EDIT
I added these two lines to remove the seconds from display as you requested in your comment.
var options = {hour: '2-digit', minute:'2-digit'};
element.innerHTML = now.toLocaleTimeString(navigator.language, options);

CSS background of png with semitransparancies transparant on a gradient shows a white background

if you look at this fiddle( http://jsfiddle.net/5ajYD/ ) with an android browser you see that the PNG that makes up the flowers has a white background.
On all other browsers it shows perfectly normal, except the android browser.
I've googled on this problem but the only thing I can find is a problem with png banding and related to android app programming.
This reminds me of the issues MSIE 6 has with transparant images, and I find it very strange that this happens.
Does anyone know a fix for this issue on android browsers?
I can't use non transparant background because of the gradient differences in different browsers.
What I have tried so far:
I have already tried using "multiple" backgrounds both posistioned at
location 0px 0px, but that doens't work
I've tried adding a gradient to to the div with the flowers, but that
failed too and broke in other browsers.
I find it very mystifying that this kind of issue shows up on a modern browser... even a nokia n95 gets it right....
The android version I’m testing against/found this with is android 2.3.4(Sony Ericsson Xperia Arc S LT18i)
This is what I see with the fiddle in the android browser on the phone
http://t.co/mofPkqjf
I had a big facepalm moment.
I've been battling with this for two months now and I simply couldn't figure out the logic. The problem was in the econtainer element that it had a parameter: width:100%.
What happens is that it only renders the width up to the actual page width of the viewport.
So if you have a browser screen on mobile that's 480px wide, it'll set width to 480px, render a gradient of 480px, and not rerender when you scroll sideways.
The problem was solved by adding a min-width:1200px; and now it renders properly!
Thank you all for looking into this...
Use HTML5 Canvas to create an alphaJPEG, a JPEG layered under an equivalent PNG with an alpha channel.
<head>
<style>img[data-alpha-src]{visibility: hidden;}</style>
</head>
<body>
<img src="image.jpg" data-alpha-src="alpha.png" />
<!--...-->
<script src="ajpeg.js"></script>
</body>
ajpeg.js
(function() {
var create_alpha_jpeg = function(img) {
var alpha_path = img.getAttribute('data-alpha-src')
if(!alpha_path) return
// Hide the original un-alpha'd
img.style.visiblity = 'hidden'
// Preload the un-alpha'd image
var image = document.createElement('img')
image.src = img.src
image.onload = function () {
// Then preload alpha mask
var alpha = document.createElement('img')
alpha.src = alpha_path
alpha.onload = function () {
var canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
img.parentNode.replaceChild(canvas, img)
// For IE7/8
if(typeof FlashCanvas != 'undefined') FlashCanvas.initElement(canvas)
// Canvas compositing code
var context = canvas.getContext('2d')
context.clearRect(0, 0, image.width, image.height)
context.drawImage(image, 0, 0, image.width, image.height)
context.globalCompositeOperation = 'xor'
context.drawImage(alpha, 0, 0, image.width, image.height)
}
}
}
// Apply this technique to every image on the page once DOM is ready
// (I just placed it at the bottom of the page for brevity)
var imgs = document.getElementsByTagName('img')
for(var i = 0; i < imgs.length; i++)
create_alpha_jpeg(imgs[i])
})();
In the head element I linked to FlashCanvas:
<!--[if lte IE 8]><script src="flashcanvas.js"></script><![endif]-->
... and I threw in this to avoid a flicker of the un-alpha’d JPEG:

PhantomJS: Webkit-Transform scale causes page to flow outside viewport

I'm trying to generate large png screenshots of web pages using PhantomJS, which is built on webkit. I have the application generating screenshots just fine (using their raster.js example.) But, I want the text to be larger (rather than 12-16px) - I don't care about the images becoming grainy. I thought that I could simply scale/zoom the webpage doing something like:
document.documentElement.style.webkitTransform = "scale(2.0)";
But that causes the content of the page to escape the viewport. You can see this if you evaluate that line of code in Chrome. Is it possible to scale a whole web page (duplicating "Ctrl +" functionality of the browser) in JavaScript/Phantom.js?
My current phantom.js script looks like:
var page = new WebPage(),
address, output, size;
if (phantom.args.length < 2 || phantom.args.length > 3) {
console.log('Usage: rasterize.js URL filename');
phantom.exit();
} else {
address = phantom.args[0];
output = phantom.args[1];
page.viewportSize = { width: 1280, height: 1024 };
page.open(address, function (status) {
if (status !== 'success') {
console.log('Unable to load the address!');
} else {
page.evaluate(function () {
document.body.style.webkitTransform = "scale(2.0)";
});
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
}
Try
page.zoomFactor=2.0;
The webkitTransform CSS property is not going to do what you want, with or without setting the origin. For one thing, it does not change the dimensions of elements (ie, no relayout occurs, the element(s) are zoomed within their current bounding boxes).
Update
You forgot to set the CSS transform-origin property, so your content expands half up and half down (the default is 50% 50%) and the upper part escapes.
So set it to 0% 0% to get the transform happen only down and right:
document.body.style.webkitTransformOrigin = "0% 0%";
You will also have to set the body width to 50% to avoid it ending twice as large as your viewport:
document.body.style.width = "50%";
Old answer - disregard
This resolves only vertical alignment
Ok, the scaling goes up and down, but the viewport extends only down. The fix fortunately is easy: move your content down of half its height.
Use this as your doubling function and you'll be fine:
page.evaluate(function() {
var h = $('body').height();
$('body').css('position', 'relative');
$('body').css('top', h/2 + 'px');
$('body').css('-webkit-transform', 'scale(2.0)');
});
Be aware anyway that getBoundingClientRect() and page.clipRect behaves weirdly when dealing with this transform.

Resources