CSS animation performance - css

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.

Related

Is there a way to position page content UNDERNEATH or ON TOP OF a scrollbar?

I'm trying to emulate the CTRL+F functionality from Chrome that highlights matches on the page in the scrollbar, but for certain fields in a form. Using page offsets and percentages, I have blocks of color which correspond to the relative locations of those fields on the page.
In my prototype, the blocks of color sit to the left of the scrollbar. Ideally, they'd sit UNDERNEATH the scrollbar, and the scrollbar's track would be transparent so that it looks like they're part of the scrollbar track.
Can the default scrollbar be set to allow for overflow content to show underneath it (or allow page content to go over it)? I know this could be accomplished if I just rolled my own scroll, but I'd like to use the default ones provided by the browser if at all possible.
It's clearest if you just look at this Prototype.
CSS:
::-webkit-scrollbar {
width: 14px;
height: 18px;
background-color:transparent;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track-piece {
background:none;
}
::-webkit-scrollbar-thumb {
height: 6px;
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
-webkit-border-radius: 7px;
background-color: #333
}
::-webkit-scrollbar-button {
width: 0;
height: 0;
display: none;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
I thought of rendering the matches on the trackbar similarly to what browsers do today before. The idea is simple by using linear-gradient background for the ::-webkit-scrollbar-track. However I did not try implementing this. Right after reading your question, I've tried it and looks like it's not such easy.
You can use the linear-gradient background OK, but if you try rendering more than 1 match (a line), it sometimes can't be rendered (especially when the window's size is changed) and the line is not rendered smoothly. Such as this seems to be OK:
//render 2 lines, one is at 50px and the other is at 100px
background: linear-gradient(transparent 50px, red 50px, red 51px, transparent 51px,
transparent 100px, red 100px, red 101px, transparent 101px);
but it's not stable, as I said when you try resizing the window, at some size, some line won't be rendered (at least I tried on Opera). When the window's height is large, the line even becomes blurred (not sharp) and thicker. I don't really understand this, because the color stops are set fixedly (by px, not by %). This issue is even worse when the number of lines is larger. You have a linear-gradient with many corresponding color stops. That seems to be a neat way to solve the problem. Just because of the undesired issue, we can't use that approach.
The new approach: So I tried using multi-backgrounds feature instead. Each background just renders 1 line, the background-size is the same for all the background is just about 2px height and the background-position should be different. Here is the equivalent code (to the above clean code) using this approach:
background: linear-gradient(red, red), linear-gradient(red, red);
background-repeat: no-repeat;
background-size: 100% 2px;
background-position: 0 50px, 0 100px;
The new approach of course requires that the browser has to support multi-backgrounds features (looks like just IE8- do not support this cool feature).
So that's almost what you need to solve this problem. Now we need to find how to apply that style using script. We can't select a pseudo-element (or something like that) via script. We can just use the window.getComputedStyle() method to get the read-only style of a pseudo-element. However we always have a way to modify the CSS directly. That's is by using pure JS with the help of document.styleSheets and cssRules. They allow us to insert/remove/modify a rule.
That looks great. But there is still another issue. When changing the style using that method, the style is not applied right (at least it happens to the ::-webkit-scrollbar-track, it may not happen to other elements). Only when you move the mouse over the scrollbar, the new style is applied. I've just found a simple way to invalidate that scrollbar by setting the overflow of document.documentElement (the html) to hidden and set it back to auto. That works almost well.
Here is the code:
var requiredTb = $(".required input");
var invalids = requiredTb;
var offsets = [];
//init offsets to highlight on the trackbar later
requiredTb.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
//find the rule styling the -webkit-scrollbar-track
//we added in the CSS stylesheet, this is done just 1 time
var sheets = document.styleSheets;
var trackRule;
for(var i = 0; i < sheets.length; i++){
var rules = sheets[i].cssRules || sheets[i].rules;
for(var j = 0; j < rules.length; j++){
var rule = rules[j];
if(rule.selectorText == "::-webkit-scrollbar-track:vertical"){
trackRule = rule; break;
}
}
}
//define an invalidate() method, we need to use this method
//to refresh the scrollbars, otherwise the newly applied style is not affected
window.invalidate = function(){
$(document.documentElement).css('overflow','hidden');
setTimeout(function(e){
$(document.documentElement).css('overflow','auto');
},1);
};
//this is the main function to set style for the scrollbar track.
function setTrackHighlights(positions, color){
positions.sort();//ensure that the input array should be ascendingly sorted.
trackRule.style.cssText = "";
var gradient = "background: ", backPos = "background-position: ";
var winHeight = $(window).height();
$.each(positions, function(i,pos){
gradient += "linear-gradient(" + color + ", " + color + "),";
backPos += "0 " + pos + "%,"
});
gradient = gradient.substr(0,gradient.length-1) + ";";
backPos = backPos.substr(0,backPos.length -1) + ";";
trackRule.style.cssText += gradient + backPos + "background-repeat:no-repeat; background-size:100% 2px";
invalidate();
}
//initially set the highlights on the trackbar
setTrackHighlights(offsets,'red');
//handle the oninput event to update the highlights accordingly
requiredTb.on('input', function(e){
var required = $(this).closest('.required');
var refreshHighlights = false;
if(this.value && !required.is('.ok')) {
required.addClass('ok');
refreshHighlights = true;
invalids = invalids.not(this);
}
if(!this.value && required.is('.ok')) {
required.removeClass('ok');
refreshHighlights = true;
invalids = invalids.add(this);
}
if(refreshHighlights){
offsets.splice(0);
invalids.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
setTrackHighlights(offsets,'red');
}
});
You have to add an empty ::-webkit-scrollbar-track:vertical rule (we need to deal only with the vertical scrollbar) in the CSS code, it should be appended at the last to override any similar rule before. We can in fact use the insertRule() method (of a CSSRuleList which can be accessed via cssRules property) to add a new rule instead of looping through the styleSheets, and through the cssRules (of each sheet) to find the empty rule ::-webkit-scrollbar-track:vertical.
The code I posted here can be improved, such as you can add another method setTrackHighlights to allow to add more lines (instead of rendering all the lines each time we need to add/remove just 1 line)...
Note that by using the term line, I mean the rendering representation of a match on the trackbar.
Demo

Css - dynamic font size

We are developing a website for a school project: this website must also be available on mobile devices (and tablets).
We have attained this goal, except for an issue with the font-size: we've set this property manually via #media query.
Does there exist a way by make the font-size can be made dynamic without the use of percent?
Thanks
If I understand your question, you should consider this option:
.yourClass {
font-size: 2.0vw;
}
It meaning 2.0% of viewport width. You can also use 2.0vh (2.0% viewport height)
I'm not sure how others would feel about this solution (since some will view it as unnecessary scripting), but it works really well for me. I use jQuery to set font sizes as a percentage of the screen size.
var FONT_SCALE = 0.027;
function initFontSizes() {
$(".text-element").css("font-size", $(window).height() * FONT_SCALE);
}
If you wanted you could add it as a jQuery function so you can call it on any object:
jQuery.fn.setFontSize(scale) {
$(this).css("font-size", $(window).height() * scale);
}
If you wanted to muddy the waters with a bit of javascript, you could go for something along the lines of:
$(function(){
resizeFont();
$(window).resize(function(){
resizeFont();
});
function resizeFont(){
var windowWidth = $(window).width(),
psize = windowWidth/ 10;
$("p").css("font-size",psize);
}
});
You'd need to a bit of maths to sort it out properly though.
http://jsfiddle.net/wildandjam/5Hnxa/

CSS Skew only container, not content

I'm having trouble figuring out how to make the following layout work. I'm not restricted to pure CSS - I know JS will be involved to make it cross-browser - but a CSS solution would be awesome. Here's what I am trying to achieve:
I've tried the following code, skewing the container and then skewing the image in the opposite direction, but it just gives me a square image. Chrome inspector shows me that the container is being skewed properly, but skewing the image back makes it square again. Adding an overflow:hidden to the container kind of works but the edges of the angle become jagged. Here's what I have tried:
http://codepen.io/anon/pen/ubrFz
Please help! :)
Need to tweak the positioning and the size of the container so you can crop it, and apply the backface-visibility rule:
.skew {
-webkit-backface-visibility : hidden; /* the magic ingredient */
-webkit-transform : skew(16deg, 0);
overflow : hidden;
width : 300px;
height : 260px;
position : relative;
left : 50px;
border : 1px solid #666
}
.skew img {
-webkit-transform : skew(-16deg, 0);
position : relative;
left : -40px;
}
http://codepen.io/anon/pen/HLtlG <- before (aliased)
http://codepen.io/anon/pen/wnlpt <- after (anti-aliased)
In lieu of a CSS solution, you could also achieve the effect by using a canvas and some JS; and compositing a series of cropped images onto that canvas. The benefit of the canvas method being that you'll potentially get smoother edges on the crops, and it is potentially a bit better supported.
A canvas element in HTML;
<canvas id="mycanvas"></canvas>
And JS;
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.src = '../my/image1.jpg';
img2.src = '../my/image2.jpg';
img3.src = '../my/image3.jpg';
var can = document.getElementById("mycanvas");
var ctx = can.getContext('2d');
var imgs = [img1, img2, img3]; //array of JS image objects that you've set up earlier
can.width = 1000;
can.height = 100;
for (var i=0; i < imgs.length; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(800 - (200 * i), 0);
ctx.lineTo(900 - (200 * i), 100);
ctx.lineTo(0, 100);
ctx.closePath();
ctx.clip();
ctx.drawImage(imgs[i], 0, 0);
}
The code is just off the top of my head - I haven't tested it. But basically - lets say you have a canvas that is a maximum of 1000px wide and 100px high. What happens above is, you set up a clipping area with a diagonal line across the canvas from point (800,0) to (900,100) and then draw the image into that clipping area... Then set up a new clipping path 200 pixels shorter for each image (note the '200 * i' bit).
Obviously the math needs to be adjusted for an arbitrary number of images and so on... But the idea is there.
A bit trickier than pure CSS maybe - but as I said - possibly a bit better supported cross-browser (IE's notwithstanding...).
EDIT
Did a quick test - looks like you need to set the canvas dimensions - and also obviously wait for all images to load properly before you can composite them on the canvas.

CSS3 webkitAnimationEnd event ordering

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.

Large background images using css

How can I load images to cover the whole background like some websites, using CSS. Not the usual background-image property but I want to load the images quickly.
Examples:
http://www.marinayachting.it/
http://alexandraowen.co.nz/
background-image is the only way to place images in CSS. If you want it to be vary large put it on the body element or a container div that fills the entire viewport.
body {
margin: 0;
padding: 0;
width: 100%;
background-image: url('my_big_image.jpg') norepeat;
}
If you use a container div you can set position:fixed; top:0; left:0 and the image will remain stationary when the page scrolls.
There's no magic to it. As far as getting it to load quickly I don't think there's much you can do if it doesn't repeat. If it does repeat then make sure your image is the size of one module. This can be as little as one pixel tall or wide depending on the content.
There is no magic to making a background image load quickly, you just:
Have a fast server.
Compress the image as much as possible.
Make your page HTML small so that the rest can start loading as soon as possible.
Don't have many other images that also has to load.
Don't have a lot of scripts and other external files that has to load.
I found this tutorial helpful. ->
http://css-tricks.com/perfect-full-page-background-image/
Bing is loading a normal background image with a fixed size. It´s not particularly fast (for me...), but perhaps it seems fast because the image is cached after the first time you load it.
You can set the style inline so that the image can start downloading without waiting for any css file to be ready.
If you set an image let's say a picture as a background you need to make it large enough to accommodate large screen sizes. You don't want the experience on your site to be, that your picture repeats multiple times on the screen. Probably at the least width should be 1260px. If background is just a simple gradient, you can cut a small part of it in photoshop and apply it on the body like this:
body {
margin:0;
padding:0;
background:#fff url(your/image/location.jpg) repeat-x scroll 0 0;
}
This method could be applied to divs too, Good luck.
In your second example site, alexandraowen.co.nz, if you took a second to look at the JS they use, you would have seen the following:
// backgrounds --------------------------------------------------------------//
var Backgrounds = {};
Backgrounds.init = function()
{
$('body').each
(
function()
{
var imgsrc = $(this).css('background-image');
if(imgsrc != 'none')
{
imgsrc = imgsrc.slice( imgsrc.indexOf('(') + 1 , -1);
$(this).css('background-image', 'none');
$(this).prepend('');
if($.browser.msie)
{
// ie 7 is the slow kid and we have to strip out quote marks ffs!
$(this).find('div.bg img').attr('src', imgsrc.split('"').join(''));
}
else
{
$(this).find('div.bg img').attr('src', imgsrc);
}
}
}
);
Backgrounds.resizeHandler();
$(window).resize(Backgrounds.resizeHandler);
$('div.bg img').load(Backgrounds.resizeHandler);
}
Backgrounds.resizeHandler = function()
{
var w = $(window).width();
var h = $(window).height();
$('div.bg img').each
(
function()
{
var wr = w / $(this).width();
var hr = h / $(this).height();
var r = Math.max(wr, hr);
var imgw = Math.round($(this).width() * r);
var imgh = Math.round($(this).height() * r);
$(this).width( imgw );
$(this).height( imgh );
var l = Math.round((w/2) - (imgw/2));
$(this).css('margin-left', l+'px');
}
);
}
As well as the HTML on the page:
<body style="background-image: none; ">
If you dig into their scripts a bit more, you can see what they did. But I guarantee you it's nothing faster than just setting the background-image property.
<img id="foo" src="bar" alt=""> with #foo { width: 100%; height: 100%; }(use position: absolute; / position: relative; & z-index for layering as desired)
Here's an old example.

Resources