Javascript style.transform = scale(x,y) animation not working - javascript

I was trying to make an animation using the style.transform property. The intention was to loop the transform scale property with slight increase in x and y on each round, but it failed. How can I achieve this effect?
x = document.getElementById("btn");
x.onclick = function() {
for (let y = 0; y < 1; y += 0.1) {
x.style.transform = 'scale(1.' + y + ',' + '1.' + y + ')';
}
}
<button id='btn'>button</button>

You should use CSS transitions.
Style your button like so:
#btn {
transition: transform 0.1s
}
That code will make the button transition during 0.1 seconds whenever the transform property is changed, for example the scale.
Then, from your JavaScript code, you juste have to assign the transform style once, and CSS will transition automatically.
x = document.getElementById("btn");
x.onclick = function() {
x.style.transform = 'scale(2,2)'; // or any x and y value
}
x = document.getElementById("btn");
x.onclick = function() {
x.style.transform = 'scale(2,2)'; // or any x and y value
}
#btn {
transition: transform 0.1s
}
<button id="btn">button</button>

You could do it with a combination of setInterval, requestAnimationFrame and CSS transitions to control the speed of the animation, good performance and a smooth controlled transition.
const button = document.getElementById("btn");
function scaleButton(speed, size = 1) {
let end = size * 2;
let interval = setInterval(() => {
if (size === end) {
clearInterval(interval);
button.style.transitionDuration = '';
}
size += 0.1;
size = parseFloat(size.toFixed(1)); // Round to 1 decimal point.
requestAnimationFrame(() => {
button.style.transform = `scale(${size}, ${size})`;
});
}, speed);
button.style.transitionDuration = `${speed}ms`;
}
button.onclick = function() {
scaleButton(50);
};
html {
width: 100%;
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
#btn {
transition: transform linear;
}
<button id='btn'>button</button>

Here is a soultion using requestAnimationFrame;
x = document.getElementById("btn");
let y = 0;
function animate (y) {
if(y < 1) {
y += 0.1;
x.style.transform = `scale(${1 + y},${1 + y})`;
requestAnimationFrame( () => animate(y) );
}
}
x.onclick = function() {
animate(0);
}
<button id='btn'>button</button>

The browser has an, how to say, an painting cycle. Every 16ms (i am not sure if its exactly 16ms) the browser does an repaint.
The problem that you have, is that your loop is already done before the next repaint cycle.
Here is an solution with async / await:
You can create an function called readyToAnimate it returns an promise that resolves the callback function of the requestAnimationFrame.
The requestAnimationFrame callback gets executed before the next repaint.
Now in your loop you can use await readyToAnimate(). It will wait till the browser is ready for the next repaint.
x = document.getElementById("btn");
x.onclick = async function() {
for (let y = 0; y < 1; y += 0.1) {
await readyToAnimate();
x.style.transform = `translateX(${y * 200}px)`;
}
}
function readyToAnimate() {
return new Promise(res => requestAnimationFrame(res));
}
<button id='btn'>button</button>

Related

How to use the counter when the counter appears on the page and not when the page is loaded

In my project, there should be a counter.
I want the counter to start when scrolling is done, not when the screen is loading.
I used "reveal" for this, but still the counter starts when the screen loads.
In the project in question, I like when scrolling the page from top to bottom and from bottom to top "reveal" arrives, the counter starts.
How can I solve this problem? Thanks in advance for your guidance.
//counter
const counter = (EL) => {
const duration = 4000; // Animate all counters equally for a better UX
const start = parseInt(EL.textContent, 10); // Get start and end values
const end = parseInt(EL.dataset.counter, 10); // PS: Use always the radix 10!
if (start === end) return; // If equal values, stop here.
const range = end - start; // Get the range
let curr = start; // Set current at start position
const loop = (raf) => {
if (raf > duration) raf = duration; // Stop the loop
const frac = raf / duration; // Get the time fraction
const step = frac * range; // Calculate the value step
curr = start + step; // Increment or Decrement current value
EL.textContent = parseInt(curr, 10); // Apply to UI as integer
if (raf < duration) requestAnimationFrame(loop); // Loop
};
requestAnimationFrame(loop); // Start the loop!
};
document.querySelectorAll("[data-counter]").forEach(counter);
//counter
// reveal
function reveal() {
var reveals = document.querySelectorAll(".reveal");
for (var i = 0; i < reveals.length; i++) {
var windowHeight = window.innerHeight;
var elementTop = reveals[i].getBoundingClientRect().top;
var elementVisible = 10;
if (elementTop < windowHeight - elementVisible) {
reveals[i].classList.add("active");
} else {
reveals[i].classList.remove("active");
}
}
}
window.addEventListener("scroll", reveal);
// reveal
.container{
overflow-y: scroll;
}
.pre-reveal{
width: 100%;
height: 80vh;
border: 1px solid red;
}
/* reveal */
.reveal {
border: 1px solid green;
position: relative;
opacity: 0;
margin-top: 1rem;
}
.reveal.active {
opacity: 1;
}
/* reveal */
<div class="container">
<div class="pre-reveal"></div>
<div class="reveal">
<span>A: </span>
<span data-counter="10">0</span>
<br>
<span>B: </span>
<span data-counter="2022">1000</span>
<br>
<span>C: </span>
<span data-counter="0">9999</span>
<br>
<span>D: </span>
<span data-counter="-1000">1000</span>
<br>
<span>E: </span>
<span data-counter="666">0</span>
<br>
</div>
</div>

How can I create a CSS animation in JavaScript?

How can I create the CSS animation below in JavaScript? I've looked all over Google, and tried multiple times to create this but I couldn't figure out how to do this.
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 100;
}
}
To run this, I know I can use what is shown below, but I don't know how to create this animation. Can anyone help?
element.style.animation = "fadeIn 5s linear";
You can use javascript with transition to achieve it
// start frame
const start = {
opacity: 0
};
// end frame
const end = {
opacity: 1
};
const element = document.querySelector('span');
Object.assign(element.style, start);
element.style.transition = 'all 5s linear';
requestAnimationFrame(() => {
Object.assign(element.style, end);
});
<span>Lorem Ipsum</span>
What do you mean exactly with "Create in Javascript"? Without using CSS?
If so, you can use a simple interval to update the opacity of the element until it reached 0 or 100. Simple example:
let opacity = 0;
const fadeEl = document.getElementById("fadeInElementIdWithOpacity0");
const fadeInInterval = setInterval(() => {
if (opacity < 1) {
opacity = opacity + 0.1
fadeEl.style.opacity = opacity;
} else {
clearInterval(fadeInInterval);
}
}, 200);
You can first define this function with whatever amount of intervals that you want and then call it with any querySelector
function fadeIn(x) {
var fade = document.querySelector(x);
var opacity = 0;
var intervalID = setInterval(function() {
if (opacity < 1) {
opacity = opacity + 0.1
fade.style.opacity = opacity;
} else {
clearInterval(intervalID);
}
}, 200);
}
havnig this function in console and running fadeIn(".-logo") will fade in the stackoverflow's logo

Smooth Scrolling, Cross-Browser, Without jQuery

Below you will find a script I created for smooth scrolling when clicking local links. It is done via transform (no jQuery). As seen, I have implemented it using both inline CSS as well as external style sheets. I recommend the inline version, as it might be difficult to guess the index of the relevant style sheet.
The problem however, is that the actual movement of the scrollbar, happens after the transform is applied. Thus, if you click a link before scrolling transition is done, the code misbehaves.
Any thoughts on a solution to this?
EDIT:
I know there are jQuery solutions and third party polyfill libraries out there. My goal, however, was to recreate the jQuery functionality in plain vanilla JavaScript.
My Script:
// Get style rule's declaration block
function getStyleDeclaration(styleSheet, selectorText) {
const rules = styleSheet.cssRules;
return Array.from(rules).find(r => r.selectorText === selectorText).style;
// for (let i = 0; i < rules.length; i += 1) {
// if (rules[i].selectorText === selectorText) return rules[i].style;
// }
}
// Get specific style sheet, based on its title
// Many style sheets do not have a title however
// Which is main reason it is preferred to work with
// inline styles instead
function getStyleSheet(title) {
const styleSheets = document.styleSheets;
return Array.from(styleSheets).find(s => s.title === title);
// for (let i = 0; i < styleSheets.length; i += 1) {
// if (styleSheets[i].title === title) return styleSheets[i];
// }
}
function scrollToElement_ExternalStyleSheet(anchor, target) {
anchor.addEventListener("click", e => {
e.preventDefault();
const time = 1000;
// Distance from viewport to topof target
const distance = -target.getBoundingClientRect().top;
// Modify external style sheet
const transStyle = getStyleDeclaration(document.styleSheets[1], ".trans");
transStyle.transform = "translate(0, " + distance + "px)";
transStyle.transition = "transform " + time + "ms ease";
const root = document.documentElement; // <html> element
root.classList.add("trans");
window.setTimeout(() => {
root.classList.remove("trans");
root.scrollTo(0, -distance + window.pageYOffset);
}, time);
});
}
function scrollToElement_InlineStyle(anchor, target) {
const root = document.documentElement;
anchor.addEventListener('click', e => {
e.preventDefault();
const time = 900;
const distance = -target.getBoundingClientRect().top;
root.style.transform = 'translate(0, ' + distance + 'px)';
root.style.transition = 'transform ' + time + 'ms ease';
window.setTimeout(() => {
root.scrollTo(0, -distance + window.pageYOffset);
root.style.transform = null; // Revert to default
root.style.transition = null;
}, time);
});
}
function applySmoothScroll() {
const anchors = document.querySelectorAll("a");
const localAnchors = Array.from(anchors).filter(
a => a.getAttribute("href").indexOf("#") != -1
);
localAnchors.forEach(a => {
const targetString = a.getAttribute("href");
const target = document.querySelector(targetString);
// scrollToElement_ExternalStyleSheet(a, target);
scrollToElement_InlineStyle(a, target);
});
}
applySmoothScroll();
.box {
padding-bottom: 300px;
padding-top: 0.5rem;
background-color: orange;
text-align: center;
font-size: 200%;
}
.box:nth-child(even) {
background-color: lightblue;
color: white;
}
a {
color: black;
}
body {
margin: 0;
}
.trans {
transform: translate(0, -100px);
transition: transform 900ms ease;
}
<div id="s0" class="box">Click Me!</div>
<div id="s1" class="box">Click Me!</div>
<div id="s2" class="box">Click Me!</div>

Javascript div width countdown

I'm trying to visualize a countdown via a div's width. This could be used for something like a banner system showing when the next banner will slide in, or for a notification system showing how long the notification will be visible.
So in my example below, I have the .outer div emptied after 5 seconds, but the .timer div's width is not reaching width: 0%; at the same time as the setTimeout() kicks in.
The variable len would represent how long the banner or notification would be shown for.
The calculation in the variable offset is what is throwing me off (I think), I cannot seem to get the calculation correct. I would like it to be dynamic, meaning, no matter what len is and what the width of the outer/parent div is, it will always take len time to reach width: 0%;.
I hope my explanation makes sense. Any help would be greatly appreciated.
const len = 5000;
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let timerWidth = timer.offsetWidth;
let offset = len / timerWidth;
let init = 100;
let interval = setInterval(() => {
init = init - offset;
timer.style.width = init + '%';
}, 1000);
setTimeout(() => {
outer.innerHTML = '';
clearInterval(interval);
}, len);
* {
box-sizing:border-box;
}
body {
padding: 100px 10px 10px;
}
.outer {
width: 100%;
border: 1px solid slategray;
padding: 10px;
}
.timer {
width: 100%;
height: 10px;
background: red;
transition: all 1000ms linear;
}
<div class="outer">
<div class="timer"></div>
<p>Some Message Here!</p>
</div>
Two problems with the code:
interval doesn't start as soon as the page is loaded so the CSS is late in transition.
offset was wrong indeed.
Here's how I fixed it:
let toElapse = 3000; // modify as you like
let tick = 1000; //if you modify this don't forget to replicate
//in CSS transition prop
let countDownEl = document.querySelector('#countdown');
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let init = 100;
// we calculate the offset based on the tick and time to elapse
let offset = init / (toElapse/tick);
countDownEl.innerHTML = init;
setTimeout(()=>{
// we need to start the first CSS transition ASAP so it is not late.
init = init - offset;
timer.style.width = init.toFixed(2) + '%';
},0)
let interval = setInterval(() => {
// the same interval.
countDownEl.innerHTML = init;
init = init - offset;
timer.style.width = init.toFixed(2) + '%';
}, tick);
setTimeout(() => {
// the same clearance timeout
outer.innerHTML = '';
clearInterval(interval);
}, toElapse);
* {
box-sizing:border-box;
}
body {
padding: 100px 10px 10px;
}
.outer {
width: 100%;
border: 1px solid slategray;
padding: 10px;
}
.timer {
width: 100%;
height: 10px;
background: red;
transition: width 1s linear;
}
<div class="outer">
<div class="timer"></div><span id="countdown"></span>
<p>Some Message Here!</p>
</div>
If you use percentage in width you don't need to know the width of your box.
you just need to substract and add on offset to timeout.
const len = 5000;
let outer = document.querySelector('.outer');
let timer = document.querySelector('.timer');
let timerWidth = timer.offsetWidth;
let offset = 100 * 1000 / 5000;
let interval = setInterval(() => {
init = init - 20;
timer.style.width = init + '%';
}, 1000);
setTimeout(() => {
outer.innerHTML = '';
clearInterval(interval);
}, len + 1000);

<meter> Color Change on Animation Health Bar

I'm making a health bar and I wish to use <meter>. I have it working except it doesn't change color as it decreases. I figured I could fix this with an if tree but I don't know how to change the color of the bar when it hits a particular width. Any ideas? Thanks!!
FIDDLE:
http://jsfiddle.net/Thaikhan/d018xzbj/13/
HTML:
<meter id="your_meter" min="0" low="30" optimum="100" high="50" max="100" value="80"> </meter>
<input type="submit" id="byBtn" value="Change" onclick="change()"/>
JavaScript:
function change(){
setTimeout(function () {
var start = $('#your_meter').attr('value');
var end = 55;
if (start > 50) {
//STARTING FROM GREEN
if (end < 50) {
//CONTINUE PAST GREEN
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = "#your_meter::-webkit-meter-optimum-value { transition: width 3s; width: 50% !important; }";
document.body.appendChild(css);
//CHANGE COLOR AND CONTINUE TO DECREASE///??????????????????
if (end < 30) {
//CONTINUE PAST YELLOW
}
else {
//STOP BEFORE YELLOW ENDS
setTimeout(function() {
var css2 = document.createElement("style");
css2.type = "text/css";
css2.innerHTML = "#your_meter::-webkit-meter-optimum-value { transition: width 3s; width: "+end+"% !important; }";
document.body.appendChild(css2);
}, 3000);
}
}
else {
//STOP BEFORE GREEN ENDS
var css = document.createElement("style");
css.type = "text/css";
css.innerHTML = "#your_meter::-webkit-meter-optimum-value { transition: width 3s; width: "+end+"% !important; }";
document.body.appendChild(css);
}
}...
The reason is because whilst you are updating the meter optimum value through CSS, you are not updating the value that the meter is actually using.
You will need to dynamically update the value of the meter's value, whilst you're updating the CSS value. You can do it like this:
document.getElementById("your_meter").value = "30";
So, to do what you're trying to do would look something like this:
function change(){
var end = 35;
var meter = document.getElementById("your_meter");
var current = meter.value;
function render(){
console.log(meter.value);
if(current <= end){
window.cancelAnimationFrame(render);
return;
}
meter.value = current - 1;
current = meter.value;
window.requestAnimationFrame(render);
}
window.requestAnimationFrame(render);
render();
}
Based on a dynamic time:
function change(){
var end = 35;
var meter = document.getElementById("your_meter");
var current = meter.value;
var interval;
var speed = 30;
function render(){
console.log(meter.value);
if(current <= end){
clearInterval(interval);
return;
}
meter.value = current - 1;
current = meter.value;
window.requestAnimationFrame(render);
}
interval = setInterval(render, speed);
}

Categories

Resources