Convert Javascript animation to CSS animation - javascript

Background
The below code adds to a DIV an animation of different sized floating circles.
The animation works well on desktop browsers, however, on an iPad tablet, all it shows is a static image, no animation.
Question
What is preventing the code from animating on tablet compared with desktop?
How do I convert the animation in Java Script to an equivalent CSS animation?
const colors = ["#000000"];
const numCircles = 50;
const circles = [];
for (let i = 0; i < numCircles; i++) {
let circle = document.createElement("div");
circle.classList.add("circle");
circle.style.background = colors[Math.floor(Math.random() * colors.length)];
circle.style.left = `${Math.floor(Math.random() * 100)}vw`;
circle.style.top = `${Math.floor(Math.random() * 100)}vh`;
circle.style.transform = `scale(${Math.random()})`;
circle.style.width = `${Math.random()}em`;
circle.style.height = circle.style.width;
circles.push(circle);
document.body.append(circle);
}
circles.forEach((el, i, ra) => {
let to = {
x: Math.random() * (i % 10 === 0 ? -10 : 10),
y: Math.random() * 10
};
let anim = el.animate(
[
{ transform: "translate(0, 0)" },
{ transform: `translate(${to.x}rem, ${to.y}rem)` }
],
{
duration: (Math.random() + 1) * 2000,
direction: "alternate",
fill: "both",
iterations: Infinity,
easing: "ease-in-out"
}
);
});
.circle {
position: absolute;
border-radius: 100%;
}
.box {
height: 500px;
width: 500px;
border: 5px outset red;
text-align: center;
}
<h1>Heading 1</h1>
<div class="box">
<h2>Heading 2</h2>
<p>This is some text inside a div element.</p>
</div>
<p>This is some text outside the div element.</p>
Image

What is preventing the code from animating on tablet compared with desktop?
Your JavaScript code is using Element.animate() which is not currently widely supported by Safari for macOS or Safari for iOS (the minimum version is iOS 13.4, which was only released 2 weeks ago).
How do I convert the animation in Java Script to an equivalent CSS animation?
You'll still need the script to create all of the <div> elements at random positions, but the animate() your code uses can be converted to a declarative CSS animation, like so:
You can run this snippet:
Note that I needed add px and s units to the --to-x, --to-y, and duration values, and I had to disable the circle.style.transform = 'scale' CSSOM rule because they were overriding the declarative CSS (the transform: scale() isn't needed anyway because the circle.style.width/height are also randomized.
const colors = ["#000000"];
function createCircles( count ) {
const circles = [];
for (let i = 0; i < count; i++) {
const circle = document.createElement("div");
circle.classList.add("circle");
circle.style.background = colors[Math.floor(Math.random() * colors.length)];
circle.style.left = `${Math.floor(Math.random() * 100)}vw`;
circle.style.top = `${Math.floor(Math.random() * 100)}vh`;
/* circle.style.transform = `scale(${Math.random()})`; <-- This is now disabled */
circle.style.width = `${Math.random()}em`;
circle.style.height = circle.style.width;
const toX = Math.random() * (i % 10 === 0 ? -100 : 100);
const toY = Math.random() * 100;
const dur = (Math.random() + 1) * 2;
circle.style.setProperty( '--to-x', toX.toFixed(0) + 'px' );
circle.style.setProperty( '--to-y', toY.toFixed(0) + 'px' );
circle.style.setProperty( '--duration', dur.toFixed(2) + 's' );
circles.push(circle);
}
return circles;
}
window.addEventListener( 'DOMContentLoaded', setup );
function setup() {
const circles = createCircles(50);
for( const circle of circles ) {
document.body.append( circle );
}
}
.circle {
position: absolute;
border-radius: 100%;
animation-name: myAnimation;
animation-duration: var(--duration);
animation-direction: alternate;
animation-fill-mode: both;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
.box {
height: 500px;
width: 500px;
border: 5px outset red;
text-align: center;
}
#keyframes myAnimation {
from {
transform: translate( 0, 0 );
}
to {
transform: translate( var(--to-x), var(--to-y) );
}
}
<h1>Heading 1</h1>
<div class="box">
<h2>Heading 2</h2>
<p>This is some text inside a div element.</p>
</div>
<p>This is some text outside the div element.</p>
This also works without any JavaScript if you render the random initial circles into your HTML directly (e.g. using PHP):
<html>
<head>
</head>
<body>
<h1>Heading 1</h1>
<div class="box">
<h2>Heading 2</h2>
<p>This is some text inside a div element.</p>
</div>
<p>This is some text outside the div element.</p>
<?php for( $i = 0; $i < 50; $i++ ) { ?>
<div class="circle" style="left: <?= rand( 0, 100 ) ?>px; top: <?= rand( 0, 100 ) ?>px; width: <?= rand( 0, 50 ) ?>px; height: <?= rand( 0, 50 ) ?>px; --to-x: <?= rand( 0, 50 ) ?>px; --to-y: <?= rand( 0, 50 ) ?>px; --duration: <?= rand( 0, 5 ) ?>s;"></div>
<?php endfor; ?>
</body>
</html>

Related

How to scale a container keeping its bottom right corner fixed?

I have a red container which initially is at bottom right of black container. I have a scale function that gradually scales the container. I want to make the bottom right position of red container to be fixed and scale it towards top left. How can I do that?
const box = document.getElementById("box")
const initHeight = 200
const initWidth = 200
const centerX = initWidth / 2
const centerY = initHeight / 2
function transform(scale, translate) {
if (translate) {
translate[0] = -centerX + translate[0]
translate[1] = -centerY + translate[1]
}
box.style.transform = `scale(${scale})${
translate ? ` translate(${translate.map((x) => x + "px").toString()})` : ""
}`
}
let initX = initWidth
let initY = initHeight
let scaleVal = 0.5
transform(scaleVal, [initX, initY])
function scale() {
scaleVal = scaleVal + 0.01
transform(scaleVal, [
initX - scaleVal * initWidth,
initY - scaleVal * initHeight
])
if (scaleVal <= 1) {
setTimeout(() => {
requestAnimationFrame(scale)
}, 50)
}
}
scale()
* {
box-sizing: border-box;
}
.box {
height: 200px;
width: 200px;
background-color: black;
position: absolute;
}
.box:nth-child(2) {
background-color: red;
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
To lock the bottom right corner of the red box to the bottom right of the black box this snippet does two things: positions red box right bottom relative to the parent app container and sets the transform-origin to that spot too (normally transform origin is at the center of an element). It then uses a CSS animation to expand the red box and contract it again using scale.
This method does not need JS as it is a simple scaling transform, but of course some of the subtleties of the original transformations are lost because of tying the corner down.
* {
box-sizing: border-box;
}
body {
position: relative;
width: 100vw;
height: 100vh;
}
#app {
position: absolute;
height: 200px;
width: 200px;
}
.box:nth-child(1) {
height: 200px;
width: 200px;
background-color: black;
position: absolute;
top: 0;
left: 0;
}
#box {
background-color: red;
width: 100px;
height: 100px;
position: absolute;
bottom: 0;
right: 0;
transform-origin: right bottom;
animation: scale 5s 1 ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
}
50% {
transform: scale(2);
}
100% {
transform: scale(1);
}
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
Okay so I finally figured it out,
const box = document.getElementById("box")
let scale = 0
const initWidth = 50
const initHeight = 50
function fixed(num, fix = 1) {
return Number(parseFloat(num).toFixed(fix))
}
function scaleBox() {
const [x, y] = [
fixed((initWidth - scale * initWidth) / 2),
fixed((initHeight - scale * initHeight) / 2)
]
box.style.transform = `translate(${x}px, ${y}px) scale(${scale})`
scale = scale + 0.1
if (scale < 1) {
setTimeout(() => {
requestAnimationFrame(scaleBox)
}, 500)
}
}
scaleBox()
* {
box-sizing: border-box;
}
.box {
height: 50px;
width: 50px;
background-color: black;
position: absolute;
}
.box:nth-child(2) {
background-color: red;
transform: translate(0, 0) scale(0);
}
<div id="app">
<div class="box"></div>
<div class="box" id="box"></div>
</div>
Explanation
The trick is to translate the container in such a way that when its scaled after the translation, it always places itself in the bottom right of purple container.
To figure out the translation amount, let's first scale the container to 0.5 without any translation. It looks like this,
As you can see the container's width is 25 as 0.5(scale) * 50(init_width)=25 and position from container from all sides(top left, bottom left, top right, bottom right) will be (25/2, 25/2)=(12.5,12.5) since the container is scaled equally from all sides.
Since the position from bottom right is (12.5,12.5), we need to translate the container to (+12.5,+12.5) and then scale it to exactly place it at bottom right.
You can achieve many things using display:flex, it's great!
This is how I would approach your problem:
const handleClick = () => {
const blackDiv = document.getElementById("black-div");
const redDiv = document.getElementById("red-div");
let widthRatio = 0;
let heightRatio = 0;
const scaleUpTimer = setInterval(() => {
if (widthRatio === 1 || heightRatio === 1) clearInterval(scaleUpTimer);
widthRatio = redDiv.offsetWidth / blackDiv.offsetWidth;
heightRatio = redDiv.offsetHeight / blackDiv.offsetHeight;
redDiv.style.width = widthRatio * 100 + 2 + "%";
redDiv.style.height = heightRatio * 100 + 2 + "%";
}, 10);
};
#black-div {
width: 200px;
height: 200px;
background-color: rgb(0, 0, 0);
display: flex;
align-items: flex-end;
justify-content: flex-end;
}
#red-div {
background-color: red;
width: 50%;
height: 50%;
}
<div id='black-div'>
<div id='red-div' onclick={handleClick()}></div>
</div>
EDIT: I used onclick here but obviously you would have to handle the situations where someone clicks the red square and its already scaled up to avoid setting unnecessary timers. Or you could just call the function directly, without having to click anything.

How to make the element stop falling?

Below this text is my code - I am trying to create the chrome dino game, and so far everything is going well, but I have one problem - after the red block jumps, the dino doesn't stop falling down - while it should. I don't understand where my problem is, as I wrote about the same code yesterday and everything worked just fine. The gravity function can be found in the JavaScript under // Gravity.
Notice how it's code is very similar to the jump function, but it doesn't work as good as the jump function. Any help would be appreciated!!!!!
// HTML Elements + Other Variables
const floor1 = document.getElementById("floor1");
const floor2 = document.getElementById("floor2");
const floor3 = document.getElementById("floor3");
const floor4 = document.getElementById("floor4");
const floor5 = document.getElementById("floor5");
const floor6 = document.getElementById("floor6");
const floor7 = document.getElementById("floor7");
const dino = document.getElementById("dino");
const highBird = document.getElementById("highBird");
const lowBird = document.getElementById("lowBird");
const wideCactus = document.getElementById("wideCactus");
const thinCactus = document.getElementById("thinCactus");
let jump = 0;
// Floor Function
setTimeout(function () {
floor1.classList.add("floor1Animation");
}, 0);
setTimeout(function () {
floor2.classList.add("floor2Animation");
}, 1000);
setTimeout(function () {
floor3.classList.add("floor3Animation");
}, 2000);
setTimeout(function () {
floor4.classList.add("floor4Animation");
}, 3000);
setTimeout(function () {
floor5.classList.add("floor5Animation");
}, 4000);
setTimeout(function () {
floor6.classList.add("floor6Animation");
}, 5000);
setTimeout(function () {
floor7.classList.add("floor7Animation");
}, 6000);
// Jump
document.onkeydown = function (event) {
let key = event.key;
if (key == "ArrowUp") {
let jumpCount = 0;
if (dino.offsetTop == 95) {
let jumpInterval = setInterval(function () {
dino.style.top = (dino.offsetTop - 5) + "px";
jumpCount += 1;
jump = true;
if (jumpCount == 20) {
clearInterval(jumpInterval);
jump = false;
jumpCount = 0;
}
}, 10);
}
}
}
// Gravity
setInterval(function () {
if (jump == false) {
let jumpGravity = setInterval(function () {
dino.style.top = (dino.offsetTop + 5) + "px";
}, 10);
if (dino.offsetTop == 95) {
clearInterval(jumpGravity);
}
}
}, 10);
body {
margin: 0;
padding: 0;
justify-content: center;
align-items: center;
display: flex;
width: 100vw;
height: 100vh;
}
#gameBoard {
width: 1000px;
height: 150px;
border: 2px solid black;
overflow: hidden;
margin: auto;
position: relative;
background-color: white;
}
#dino {
width: 30px;
height: 50px;
background-color: red;
left: 10px;
top: 95px;
position: absolute;
}
.floorBackground {
position: relative;
height: 10px;
width: 200px;
display: flex;
justify-content: center;
align-items: center;
}
.floor {
position: absolute;
top: 140px;
height: 10px;
width: 200px;
}
#floor1 {
right: -200px;
background-color: red;
}
.floor1Animation {
animation: floorAnimation 6s infinite linear;
}
#floor2 {
right: -200px;
background-color: blue;
}
.floor2Animation {
animation: floorAnimation 6s infinite linear;
}
#floor3 {
right: -200px;
background-color: green;
}
.floor3Animation {
animation: floorAnimation 6s infinite linear;
}
#floor4 {
right: -200px;
background-color: purple;
}
.floor4Animation {
animation: floorAnimation 6s infinite linear;
}
#floor5 {
right: -200px;
background-color: brown;
}
.floor5Animation {
animation: floorAnimation 6s infinite linear;
}
#floor6 {
right: -200px;
background-color: orange;
}
.floor6Animation {
animation: floorAnimation 6s infinite linear;
}
#floor7 {
right: -200px;
background-color: yellow;
}
.floor7Animation {
animation: floorAnimation 6s infinite linear;
}
#keyframes floorAnimation {
from {
right: -200px;
}
to {
right: 1000px;
}
}
#keyframes jumping {
}
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8">
<title>
Dino Game
</title>
<link rel = "stylesheet" type = "text/css" href = "DinoCSS.css">
</head>
<body>
<div id = "gameBoard">
<div id = "floor1" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor2" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor3" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor4" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor5" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor6" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "floor7" class = "floor">
<img src = "Pictures/dinoBackground.PNG" class = "floorBackground">
</div>
<div id = "dino"></div>
<div id = "highBird"></div>
<div id = "lowBird"></div>
<div id = "wideCactus"></div>
<div id = "thinCactus"></div>
</div>
<script type = "text/javascript" src = "DinoJS.js"></script>
</body>
</html>
You should try simplifying your code, it may make the issue easier to find or it may even remove it entirely.
I'd suggest using a velocity system, where every frame you adjust the dino's offsetTop by the dino's current velocity, and then subtract the gravity amount from the dino's current velocity.
This causes the velocity to decrease at a constant rate, but the dino's offsetTop position to decrease at an exponential pace, mimicking real gravity.
Then, to make collision work, just test if the dino is at or above the correct "offsetTop" value before subtracting the gravity. If you find that the dino is below the ground, just set the offsetTop to the ground level and clear the velocity.
I'd also suggest moving your game over to the HTML5 Canvas API, since then you just have to deal with properties of objects rather than DOM element style attributes.
Velocity example with canvas:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 10;
var y = 10;
var VelocityX = 0;
var VelocityY = 0;
const gravity = 0.75;
// width and height of the square
var width = 10;
var height = 10;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
x += VelocityX;
if( x > 500) {
x = 0;
}
if ( y >= 450 ) {
y = 450
VelocityY = 0
} else {
y += VelocityY;
VelocityY += gravity; // Higher values = lower position on canvas
}
ctx.fillRect(x, y, width, height);
}
setInterval(animate, 1000 / 60); // 60 FPS
#canvas {
border: 1px solid black;
}
<canvas id="canvas" width="500" height="500"></canvas>
JSFiddle

How To Apply Animation Script To Square Box DIV In HTML

Background
The below code creates a square box <div class="box"> and adds an animation of different sized circles.
At the moment, the script creates an animation across the whole page, but I’m trying to apply the animation script specifically to the square box <div class="box">.
I’ve tried changing the script document.body.append(circle) to document.getElementsByClassName("box")[0].appendChild(circle) with no success, the animation continues to apply across the whole page.
Question
What code changes are needed to apply the Script animation specifically to <div class="box"> ?
Code
<html>
<h1>Heading 1</h1>
<div class="box">
<h2>Heading 2</h2>
<p>This is some text inside a div element.</p>
</div>
<p>This is some text outside the div element.</p>
</html>
<style>
.circle {
position: absolute;
border-radius: 100%;
}
.box {
height: 500px;
width: 500px;
border: 5px outset red;
text-align: center;
}
</style>
<script>
const colors = ["#000000"];
const numCircles = 50;
const circles = [];
for (let i = 0; i < numCircles; i++) {
let circle = document.createElement("div");
circle.classList.add("circle");
circle.style.background = colors[Math.floor(Math.random() * colors.length)];
circle.style.left = `${Math.floor(Math.random() * 100)}vw`;
circle.style.top = `${Math.floor(Math.random() * 100)}vh`;
circle.style.transform = `scale(${Math.random()})`;
circle.style.width = `${Math.random()}em`;
circle.style.height = circle.style.width;
circles.push(circle);
document.body.append(circle);
}
circles.forEach((el, i, ra) => {
let to = {
x: Math.random() * (i % 10 === 0 ? -10 : 10),
y: Math.random() * 10
};
let anim = el.animate(
[
{ transform: "translate(0, 0)" },
{ transform: `translate(${to.x}rem, ${to.y}rem)` }
],
{
duration: (Math.random() + 1) * 2000,
direction: "alternate",
fill: "both",
iterations: Infinity,
easing: "ease-in-out"
}
);
});
</script>
Image
The problem is in
circle.style.left = `${Math.floor(Math.random() * 100)}vw`;
circle.style.top = `${Math.floor(Math.random() * 100)}vh`;
vw and vh use the full page width and full page height.
Change them to % AND replace document.body.append(circle); with document.querySelector('.box').append(circle). Also make sure the .box has position:relative to make sure the absolute positioning stays inside the box.
Result
step1: changing the script document.body.append(circle) to document.getElementsByClassName("box")[0].appendChild(circle)
step2: add below css to .box;
{
transform: scale(1);
overflow: hidden;
}
Result

jquery animate doesn't work in IE9 and older

I have problem with jquery animation. It's not working in IE9 and older. I think problem is somewhere with css because as I can see in a ie there is opacity set to 0.
Javascript
var $lead = $('.lead');
var height = $lead.height();
var totalHeight = height * numOfLeads;
function bounce() {
var time = 400;
var counter1 = 1;
$($(".lead").get().reverse()).each(function() {
setTimeout(function(el, counter1, height, totalHeight) {
$(el).css({
top: "-" + (totalHeight - (counter1 * height) + height) + "px",
opacity: 0,
display: "block",
position: "relative"
}).animate({
top: "+=" + (totalHeight - (counter1 * height) + height) + "px",
opacity: 1
}, 1000, "easeOutBounce")
;
}, time, this, counter1, height, totalHeight);
time += 400;
counter1 += 1;
});
}
bounce();
HTML
<div class="lead">
<div class="progress-bar">
<div>
<span class="first green end"></span>
<span class="middle"></span>
<span class="middle"></span>
<span class="last"></span>
</div>
<span>Accepted</span>
</div>
<div class="product">Hypotek</div>
<div class="county">Ustecky</div>
<div class="change">
<span>Changed</span>
<div>22</div>
<div>29</div>
<div>38</div>
</div>
CSS
#leads{position: absolute;}
#leads, #leads div { z-index: 5;}
.lead { background: url("../images/lead_back_stripe.png") repeat-x scroll 0 0 transparent; height: 65px;}
.lead > div { float: left; padding-left: 20px; padding-top: 21px; width: 180px;}
.lead > div.progress-bar{width: 185px;}
.lead > div.product {width: 175px;}
You can see the whole thing here
Does anybody know where the problem is?
long story short: opacity won't work in ie and the only method to set an opacity in ie is, as far as i know, filter: alpha(opacity = X); with 0 < X > 100 ....which won't work with an jquery animation
except, maybe this works:
$(el).animate({
opacity:1
},{
step: function( now, fx ) {
var X = 100 * now; //like at half the animation, opacity is 0.5, so X becomes 50
$(fx.elem).css("filter","alpha(opacity="+X+")");
}
});

add HTML5 Canvas b&w effect to more than one image

I am wanting to create a gallery using HTML5 Canvas Black for hover effects however I am having a bit of trouble.
I can get the effect work perfectly on one element but I can't seem to get it to work on more than one.
Here is what I have:
(function() {
var supportsCanvas = !!document.createElement('canvas').getContext;
supportsCanvas && (window.onload = greyImages);
function greyImages() {
var ctx = document.getElementsByTagName("canvas")[0].getContext('2d'),
img = document.getElementById("cvs-src"),
imageData, px, length, i = 0,
grey;
ctx.drawImage(img, 0, 0);
// Set 500,500 to the width and height of your image.
imageData = ctx.getImageData(0, 0, 378, 225);
px = imageData.data;
length = px.length;
for ( ; i < length; i+= 4 ) {
grey = px[i] * .1 + px[i+1] * .1 + px[i+2] * .1;
px[i] = px[i+1] = px[i+2] = grey;
}
ctx.putImageData(imageData, 0, 0);
}
})();
HTML:
<article id="respond" class="first">
<a href="gitano.php" rel="#overlay" style="text-decoration:none">
<img id="cvs-src" src="images/thumbnails/regular/gitano.png" />
<canvas width=378 height=225></canvas>
<div class="caption">
<p><img class="enlarge" src="images/enlarge.png"/>Click To <em>View Project</em></p>
</div>
</a>
</article>
CSS:
figure article img {
width: 100%;
padding: 0;
margin: 0;
border: 0;
vertical-align:bottom;
}
figure article:hover img {
display: block;
}
figure article canvas {
width: 100%;
padding: 0;
margin: 0;
border: 0;
position: absolute;
left: 0;
top: 0;
opacity: 1;
-webkit-transition: all 1s;
-moz-transition: all 1s;
-o-transition: all 1s;
-ms-transition: all 1s;
transition: all 1s;
}
figure article canvas:hover {
opacity: 0;
}
It looks like you are applying it to only one canvas here:
var ctx = document.getElementsByTagName("canvas")[0].getContext('2d')
You are fetching the first canvas ([0]) and getting its context. Perhaps this would be better:
var canvases = document.getElementsByTagName("canvas");
for (var j = 0; j < canvases.length; j ++) {
var ctx = canvases[j].getContext('2d'),
img = document.getElementById("cvs-src"),
imageData, px, length, i = 0,
grey;
// and so on...
}

Categories