CSS Transition on Png Sequence Using Steps

CSS transition on png sequence using steps

It's not a perfect solution, but you can have it somehow working handling the hover change with an animation instead.

It will work ok if you un-hover after the animation has finished, but it won't animate at all if you un-hover quickly

CSS

div { width:50px; 
height:72px; background:url(http://openbookproject.net/thinkcs/python/english3e/_images/duke_spritesheet.png) left top;
transition:background 1.5s steps(9, end);
background-position: 0px top;
-webkit-animation: none;
}

div:hover {
background-position:-450px top;
-webkit-animation: bkg 1.5s steps(9);
transition: none;

}

@-webkit-keyframes bkg {
0% {background-position: 0px top;}
100% {background-position: -450px top;}
}

CSS demo

And, a solution using jQuery. This one rewinds the animation from the point where it was at the un-hover moment. It also recalculates the time, so that it doesn't slow down

jQuery

$( "div" ).hover(
function() {
var elem = $(this);
var cur = parseInt(elem.css("background-position-x"));
var steps = (450 + cur) / 50;
var time = steps * 0.1;
var trans = "background " + time + "s steps(" + steps + ", end)";
elem.css("transition", trans);
elem.css("background-position", "-450px 0px");
},
function() {
var elem = $(this);
var cur = parseInt(elem.css("background-position-x"));
var steps = - cur / 50;
var time = steps * 0.1;
var trans = "background " + time + "s steps(" + steps + ", end)";
elem.css("transition", trans);
elem.css("background-position", "0px 0px");
}
);

jQuery demo

The jQuery code can be streamlined a little, using functions to avoid duplicate code, but I have set it to be as clear as posible

How to use the PNG sequence in a single file in HTML / Javascript?

Sorry, but I can animate horizontally (or vertically) only. If you stack your images side to side horizontally, it will be perfect.

.animatedImage {
background-image: url(https://i.stack.imgur.com/kuEev.png);
height: 300px;
width: 500px;

animation: png-animation 1s steps(3) forwards; /* To change speed of animation, change firs number; To include more images, change number in steps() */
animation-iteration-count:infinite; /* Make animation never ends */
}

@keyframes png-animation {

to {
background-position: -1500px 0px;
}

}
<div class="animatedImage"></div>

CSS transform: matrix3d() - After transition, image pops back into place instead of easing?

By applying a transition to any pseudo-class, such as :hover, that transition will only be applied when the element is in the associated state.

To achieve what you want you would therefore need to apply the transition to the .rot class:

.rot {  width: 100px;  height: 100px;  background-color: black;  transition:transform 2s ease;}.rot:hover {  transform: matrix3d(0.9659258262890683, 0, 0.25881904510252074, 0, 0, 1, 0, 0, -0.25881904510252074, 0, 0.9659258262890683, 0, 0, 0, 0, 1);}
<div class="rot"></div>

CSS animations with Spritesheets in a grid image (not in a row)

Since this can be a difficult to debug task, I would like to start with the same problem, but in an easier to debug environment.

I chose to do it as a rectangle animation over the full image.

.hi {    width: 320px;    height: 315px;    background-image: url("http://i.stack.imgur.com/CjMscm.jpg");    position: relative;    border: solid 1px black;}
.hi:before { content: ""; position: absolute; width: 100%; height: 53px; left: 0px; top: 0px; border: solid 1px red; -webkit-animation: playv 18s steps(6) infinite; }
@-webkit-keyframes playv { 0% { top: 0px; } 100% { top: 315px; }}
.hi:after { content: ""; position: absolute; width: 53px; height: 100%; left: 266px; top: 0px; border: solid 1px red; -webkit-animation: playh 3s steps(6) infinite; }
@-webkit-keyframes playh { 0% { left: 0px; } 100% { left: 320px; }}
<div class="hi"></div>

Render CSS/Javascript animation as a sequence of image files with Selenium using Python

EDIT4:

As per @Arakis pointed out, what you want (enough screenshots about any given animation to worth making a video) is not possible without taking over the animation in the browser. If you are not able to take over the animation, this is the best you can get:

  1. load up the page
  2. while the animation lasts, create as many screenshots as possible
  3. save the data
import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_not_wanted\{}.png'
WINDOW_SIZE = 1920, 1080

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

shots = []
if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
os.makedirs(os.path.dirname(TARGET_FOLDER))

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')

# we know that the animation duration on this page is 1 sec so we will try to record as many frames as possible in 1 sec
start_time = time.time()
while time.time() <= start_time + 1:
shots.append(driver.get_screenshot_as_png())

# dumping all captured frames
for i in range(len(shots)):
with open(TARGET_FOLDER.format(i), "wb") as f:
f.write(shots[i])

If you run this code above, you will get approx 3 screenshots, maybe 4 if you have a beast machine.

Why is this happening? When selenium grabs a screenshot, the animation in the browser won't stop and wonder if is it feasible to move on, it just runs independent as normal. Selenium in this resolution (1920x1080) is capable of grabbing 3-4 screenshots in a second. If you reduce the screen resolution to 640x480, you'll get 5-7 screenshots per second depending on your machine, but that frame rate is still very-very far form what you probably desire. Selenium interacts with a browser trough an API but does not control the browser. Taking a screenshot takes time and while selenium grabs the rendered page as an image, animation moves on.

If you want to have a smooth animation with high frame rate, you have to take control over the animation by overriding the animation states. You have to:

  • set the animation-play-state to paused
  • set the animation-duration to a reasonable length
  • set the animation-delay to a negative value to select a given animation state
  • in a for loop adjust the animation-delay and grab a screenshot
  • save screenshot data later

On the given page (https://daneden.github.io/animate.css/) in the opening animation there are 2 webelements animated with the same animation. So what the code below does is the list above with the little extra: it 'steps' the animation cycle for both elements.

import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_wanted\{}.png'
WINDOW_SIZE = 1920, 1080
ANIM_DURATION = 2
FRAMES = 60
BASE_SCR = 'arguments[0].setAttribute("style", "display: block;animation-delay: {}s;animation-duration: {}s; animation-iteration-count: infinite;animation-play-state: paused;")'

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')

header = driver.find_element_by_xpath('//*[@class="site__header island"]')
content = driver.find_element_by_xpath('//*[@class="site__content island"]')

shots = []
for frame in range(FRAMES):
for elem in (header, content):
driver.execute_script(BASE_SCR.format((frame / FRAMES) * -ANIM_DURATION, ANIM_DURATION), elem)
shots.append(driver.get_screenshot_as_png())

if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
os.makedirs(os.path.dirname(TARGET_FOLDER))

# dumping all captured frames
for i in range(len(shots)):
with open(TARGET_FOLDER.format(i), "wb") as f:
f.write(shots[i])

This is the best you can get with selenium.

BONUS: (or edit5?)

I was wondering if is really impossible to squeeze out more than 5-6 frames per second with a completely generic solution (webpage-independent). I was thinking on this:

  • there are JS libraries which are capable of converting html elements into images
  • it is possible to inject JS to the page with selenium
  • since there are no API calls , a pure JS screenshot logic could outperform the pure selenium approach

So I decided to:

  • inject the JS library to the page
  • reload the document.body to reset the animation
  • grab the screenshots and save them

This is the code, contains more JS than python :D :

import base64
import os
import time

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

TARGET_FOLDER = r'C:\test\animated_you_actually_wanted\{}.png'
WINDOW_SIZE = 800, 600
ANIM_DURATION = 1
FRAMES = 15
BASE_SCR = """
function load_script(){
let ss = document.createElement("script");
ss.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js";
ss.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(ss);
};

load_script();
shots = [];

window.take = function() {
html2canvas(document.body, {
onrendered: function (canvas) {
shots.push(canvas);
}
})
};

window.record = function(times, sleep){
for (let i=0; i<times; i++){
setTimeout(window.take(), sleep*i)
console.log("issued screenshot with sleep: " + sleep*i)
}
};
""" + """
document.body.setAttribute("style", "width: {width}px; height: {height}px");
""".format(width=WINDOW_SIZE[1], height=WINDOW_SIZE[0])

RECORD_SCR = """
document.body.innerHTML = document.body.innerHTML
window.record({}, {})
"""

GRAB_SCR = """

function getShots(){
let retval = []
for (let i=0; i<shots.length; i++){
retval.push(shots[i].toDataURL("image/png"))
}
return retval;
}

return getShots()
"""

options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("start-maximized")
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")

if not os.path.isdir(os.path.dirname(TARGET_FOLDER)):
os.makedirs(os.path.dirname(TARGET_FOLDER))

driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe', options=options,
service_args=['--ignore-ssl-errors=true', '--ssl-protocol=any'])
# driver = webdriver.Chrome(executable_path=r'c:\_\chromedriver.exe')
driver.set_window_size(*WINDOW_SIZE)
driver.get('https://daneden.github.io/animate.css/')

driver.execute_script(BASE_SCR)
time.sleep(3)
driver.execute_script(RECORD_SCR.format(FRAMES, (ANIM_DURATION/FRAMES)*100))

shots = []
while len(shots) < FRAMES:
shots = driver.execute_script(GRAB_SCR)

# dumping all captured frames
for i in range(len(shots)):
with open(TARGET_FOLDER.format(i), "wb") as f:
f.write(base64.urlsafe_b64decode(shots[i].split('base64,')[1]))

driver.quit()

Outcome of the html2canvas experiment:

  • the sweet-spot regarding frame rate is around 15-ish on the page mentioned above. More frames cause bottlenecks, especially in the first few frames where the text float-in initial position increases the document.body size
  • target element (document.body) dimension heavily impacts the performance
  • bottlenecks everywhere. I've tried to avoid magic numbers, wasn't easy
  • rendered image may contain glitches, like texts outside their container
  • it may be worth trying a different library, others may perform better
  • this result is still lightyears away from the 60fps frame rate one can achieve with a non-generic solution
  • one can get more frames in headless mode

How to create a stepped animation from a strip?

Here's my CSS for a loading animation from my site. Since you did not provide any images, I'll provide mine as an example.

Pay attention to the animate property. The synatx is as follows:

animation: name duration timing-function delay iteration-count direction;

In my case direction is omitted.

Image:
loader

.loader {
display: inline-block;
width: 32px !important;
height: 32px !important;
background-image: url("loader.png");
background-repeat: no-repeat;
background-size: 2400px 32px;
animation: play16 3.25s steps(75) infinite;
}
@keyframes play32 {
from { background-position: 0px; }
to { background-position: -2400px; }
}

In the steps(75), the number has to match the amount of 'frames' your sprite contains, otherwise the animation will cut off or some frames will repeat.

I use prefix-free to eliminate the need for vendor prefixes. You can either use this, or prefix the code manually.

How to show animated image from PNG image using javascript? [ like gmail ]

I leave you a rough example so you can get a starting point:

I will use a simple div element, with the width and height that the animated image will have, the png sprite as background-image and background-repeat set to no-repeat

CSS Needed:

#anim {
width: 14px; height: 14px;
background-image: url(https://ssl.gstatic.com/ui/v1/icons/mail/im/emotisprites/wink2.png);
background-repeat: no-repeat;
}

Markup needed:

<div id="anim"></div>

The trick is basically to scroll the background image sprite up, using the background-position CSS property.

We need to know the height of the animated image (to know how much we will scroll up each time) and how many times to scroll (how many frames will have the animation).

JavaScript implementation:

var scrollUp = (function () {
var timerId; // stored timer in case you want to use clearInterval later

return function (height, times, element) {
var i = 0; // a simple counter
timerId = setInterval(function () {
if (i > times) // if the last frame is reached, set counter to zero
i = 0;
element.style.backgroundPosition = "0px -" + i * height + 'px'; //scroll up
i++;
}, 100); // every 100 milliseconds
};
})();

// start animation:
scrollUp(14, 42, document.getElementById('anim'))

EDIT: You can also set the CSS properties programmatically so you don't have to define any style on your page, and make a constructor function from the above example, that will allow you to show multiple sprite animations simultaneously:

Usage:

var wink = new SpriteAnim({
width: 14,
height: 14,
frames: 42,
sprite: "https://ssl.gstatic.com/ui/v1/icons/mail/im/emotisprites/wink2.png",
elementId : "anim1"
});

var monkey = new SpriteAnim({
width: 18,
height: 14,
frames: 90,
sprite: "https://ssl.gstatic.com/ui/v1/icons/mail/im/emotisprites/monkey1.png",
elementId : "anim4"
});

Implementation:

function SpriteAnim (options) {
var timerId, i = 0,
element = document.getElementById(options.elementId);

element.style.width = options.width + "px";
element.style.height = options.height + "px";
element.style.backgroundRepeat = "no-repeat";
element.style.backgroundImage = "url(" + options.sprite + ")";

timerId = setInterval(function () {
if (i >= options.frames) {
i = 0;
}
element.style.backgroundPosition = "0px -" + i * options.height + "px";
i++;
}, 100);

this.stopAnimation = function () {
clearInterval(timerId);
};
}

Notice that I added a stopAnimation method, so you can later stop a specified animation just by calling it, for example:

monkey.stopAnimation();

Check the above example here.



Related Topics



Leave a reply



Submit