I have a code which draws a circle on the screen using the css transition, when the transition is finished, it triggers something else. In the code below the transitioned event doesn't work.
Part1: css
<style>
.circle {
transition-property: width, height;
transition-duration: 2s;
position: fixed;
left = 100px;
top = 100px;
background-color: red;
border-radius: 50%;
}
</style>
Part2: Javascript:
<script >
function changeRadius(radius) {
let div = document.createElement('div');
div.className = 'circle';
document.body.append(div);
div.style.width = div.style.height = 0;
console.log(div.style.width);
div.addEventListener("transitionend", function () {
alert('transitionend event!');
});
div.style.width = div.style.height = 2 * radius + 'px';
console.log(div.style.width);
}
changeRadius(100);
The 'transitioned event' is not triggered even if the 'console.log' showed the 'width' property of div has changed to 200px. I have to wrap the last part in the setTimeout function as below:
setTimeout(() => {
div.style.width = div.style.height = 2 * radius + 'px';
}, 0);
Now it works!
May someone knows the inner logic behind this. My assumption is: when the code is implemented, even if the style property inside a function changes, it's not considered as a transitioned event. However, the code outside this block to change the style property will work, such as 'setTimeout' or 'mouse hover' etc. I would like to get some deeper interpretation of this!
Related
I have a little mouse speed detector (which is far from perfect) which gives me the current mouse speed every 100ms in the variable window.mouseSpeed.t.
I only implemented it because I want to have a funny animation on the bottom edge of the screen with a bar that grows with higher speeds and shrinks with lower speeds.
I want it to be animated with Element.animate().
The only problem is: How can I change the Animation's end keyframe (I only give an end frame so the browser assumes the current status as the first frame) while the animation is running?
I want to achieve that the bar smoothly changes its length.
// The code I want to have animated is below this function.
// Mouse speed tracker (I hope this isn't too horrible code):
document.addEventListener('DOMContentLoaded', mausgeschwindigkeitVerfolgen, {once:true});
function mausgeschwindigkeitVerfolgen() { // "Mausgeschwindigkeit verfolgen" means "track mouse speed" in German
var speedX = NaN;
var speedY = NaN;
var posX = NaN;
var posY = NaN;
var speed = NaN;
document.addEventListener("mousemove", function(ev){
speedX += Math.abs(ev.movementX);
speedY += Math.abs(ev.movementY);
speed = 10*Math.sqrt(ev.movementX**2+ev.movementY**2);
window.mousePosition = {x:posX = ev.clientX,y:posY = ev.clientY};
}, false);
setInterval(function(){
[window.mouseSpeed, window.mousePosition] = [{x:speedX,y:speedY,t:speed}, {x:posX,y:posY}]; // Werte in window.mouseSpeed und window.mouseDistance speichern
speed = totalX = totalY = 0;
}, 100);
window.mausgeschwindigkeitVerfolgen = () => {return {speed:window.mouseSpeed, pos:window.mousePosition};};
return {speed:window.mouseSpeed, pos:window.mousePosition};
}
// --- This is the code I want to have animated: ---
setInterval(() => {
document.querySelector('div#mouseSpeedIndicator').style.width = window.mouseSpeed.t+'px';
//document.querySelector('div#mouseSpeedIndicator').animate({width:'0px'}, {duration:1000,iterations:1}); // This just keeps the bar at width 0, I want it to slowly change to any newly set width
}, 100);
div#mouseSpeedIndicator {
position: fixed;
bottom: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-top-right-radius: 10px;
}
<!-- What I currently have -->
<div id="mouseSpeedIndicator"></div>
First, something as simple as one additional line of the transition CSS property like e.g. ...
transition: width 1s ease-out;
... already does the job; no need for more JavaScript based computation and DOM manipulation.
But of cause the OP's script could be dramatically simplified with or even without the support of an external helper method like throttle (lodash _.throttle or underscorejs _.throttle) where the latter would create a delayed executed version of the passed function which for the OP's example-script is the 'mousemove'-handler.
This handler before being throttled (or even not throttled) could be created as a bound version of the function which actually computes the speed value and updates the indicator-node's appearance.
function handleMouseSpeedIndicatorUpdateFromBoundData(evt) {
const { movementX, movementY } = evt;
const { rootNode, timeoutId } = this;
// prevent indicator nullification at time.
clearTimeout(timeoutId);
// compute `speed`.
const speed = 10 * Math.sqrt(movementX**2 + movementY**2);
// update indicator appearance.
rootNode.style.width = `${ speed }px`;
// trigger delayed indicator nullification.
this.timeoutId = setTimeout(() => rootNode.style.width = 0, 110);
}
function initialzeMouseSpeedIndicator() {
document
.addEventListener(
'mousemove',
// create throttled version of the just created bound handler.
_.throttle(
// create handler function with bound contextual data.
handleMouseSpeedIndicatorUpdateFromBoundData.bind({
rootNode: document.querySelector('#mouseSpeedIndicator'),
timeoutId: null,
}), 100
),
false
);
}
// - no need for `'DOMContentLoaded'`
// in order to initialize the indicator.
initialzeMouseSpeedIndicator();
div#mouseSpeedIndicator {
position: fixed;
top: 0px;
left: 0px;
height: 33px;
background-color: green;
max-width: 100vh;
border: 0px solid green;
border-bottom-right-radius: 10px;
/* proposed change(s) */
transition: width 1s ease-out;
/* transition: width .5s ease-in; */
/* transition: width .5s ease-in-out; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<div id="mouseSpeedIndicator"></div>
Im trying to get the shapes to appear once after clicking. After 1.2 seconds it appears, then when clicked it disappears, then the whole process repeats. The problem is after a few clicks two shapes appear.
This is the link to the program.
https://jsfiddle.net/EyedFox/w98naLjx/2/
function show() {
var randomColor = colors[Math.floor(colors.length * Math.random())]; // Random color chosen
var randomX = Math.floor(Math.random() * 60); // random x axis margin
var randomY = Math.floor(Math.random() * 80); // random y axis margin
square = document.getElementById("square");
circle = document.getElementById("circle");
var shapeArray = [square, circle];
var randShape = shapeArray[Math.floor(shapeArray.length * Math.random())];
randShape.style.margin = randomX + "% " + randomY + "%";
randShape.style.backgroundColor = randomColor;
randShape.style.display = "block";
randShape.addEventListener("click", click);
function click() {
randShape.style.display = "none";
setTimeout(show, 1200);
}
}
setTimeout(show, 1200);
Your code keep adding click event listener to randShape, but it never cleans them those event. As a result, they keep being added and click (and thus show) will get executed several times per click. Try to add console.log('clicked on', randShape); at the beginning of your click function to see it. If by chance all the show execution selects the same shape, you'll see only this one on screen, but otherwise you'll get both.
Your click function should look like this:
function click() {
console.log('clicked on', randShape);
randShape.style.display = "none";
setTimeout(show, 1200);
randShape.removeEventListener('click', click); // cleaning after myself ;-)
}
(btw (function() { ... })() is not executed on load, but as soon as it is encountered by the javascript vm)
Here is a fiddle that fixes the issue.
Edited my response based on #autra response
I would probably completely clean the canvas before redrawing:
https://jsfiddle.net/c0L54uk3/
Essentially made a reset function like so:
function reset(){
square = document.getElementById("square").style.display = "none";
circle = document.getElementById("circle").style.display = "none";
randShape.removeEventListener("click", click);
}
And called it at the beginning of the click() function to reset the canvas like so:
function click() {
reset();
setTimeout(show, 1200);
}
Hope that helps.
Delegate Events Instead of Registering Them Individually
addEventListener() should be outside the callback function (i.e. show()). There's an event listener being added to the clicked tag but none of them are being removed. So when the 4th to 7th click occurs, the browser starts slowing down and that setTimeout is never 1.2 seconds because it will be cued to fire off which means it'll wait however long then wait 1.2 seconds. As the cue accumulates, the clicks will out race the setTimeout.
Register the parent tag, .container to listen to the click event event for all of its children tags. This pattern is called Event Delegation. In doing so, you can add the click event to an unlimited number of tags within .container using only one event listener instead of one for each shape.
The #triangle was never styled so it's an extra invisible div that floats around. In the demo I have styled the #triangle and added a .blank <div> outlined in gold (you can remove the .blank).
Demo
Fiddle
Details commented in demo
// Reference parent tag
var box = document.querySelector('.container');
function show() {
// Collect all .shape into a NodeList and convert into an array
var shapes = Array.from(document.querySelectorAll('.shape'));
var colors = ["yellow", "red", "green", "purple", "aqua", "chartreuse", "coral", "dodgerBlue", "deepPink"];
var rColor = colors[Math.floor(colors.length * Math.random())];
var rX = Math.floor(Math.random() * 60);
var rY = Math.floor(Math.random() * 80);
var rShape = shapes[Math.floor(shapes.length * Math.random())];
rShape.style.margin = rX + "% " + rY + "%";
// if #triangle change the border-bottom-color
if (rShape.id === 'triangle') {
rShape.style.borderBottomColor = rColor;
// otherwise change background-color
} else {
rShape.style.backgroundColor = rColor;
}
rShape.style.display = "block";
}
/*
Run show()
A setTimeout nor an IIFE is needed to initially run show()
Simply call it.
*/
show();
// Register box to click event...
box.addEventListener('click', function(e) {
// ...reference the clicked tag (i.e. .shape)...
var tgt = e.target;
// ...reference the registered parent tag...
var cur = e.currentTarget;
/*
...if the clicked tag IS NOT the registered parent tag...
*/
if (tgt !== cur) {
// ...then hide clicked element...
tgt.style.display = "none";
// ...and run show() in about 1.2 seconds
setTimeout(show(), 1200);
}
});
.container {
overflow: hidden;
height: 800px;
width: 80%;
background-color: rgb(0, 34, 85);
margin: 0 auto;
margin-top: 5em;
}
#square {
width: 80px;
height: 80px;
background-color: red;
margin-left: 0%;
margin-top: 10%;
display: none
}
#circle {
width: 100px;
height: 100px;
background: red;
border-radius: 50px;
display: none
}
#triangle {
width: 0;
height: 0;
border-left: 45px solid transparent;
border-right: 45px solid transparent;
border-bottom: 90px solid cyan;
display: none
}
.shape {
cursor: pointer
}
.blank {
outline: 10px solid gold
}
<div class="container">
<div id="square" class='shape'></div>
<div id="triangle" class='shape'></div>
<!--
This .blank represents what #triangle was before it
had any style.
-->
<div class='blank'></div>
<div id="circle" class='shape'></div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" />
I've already asked this question but it did not solve my problem (JavaScript / CSS - How can I start an animation, creating new Divs from cursor position?).
I've found a solution but it doesn't seem to work properly on Mozilla and Opera. Also, when I click multiple times, the div starts flashing, which is not what I want. I need to have more than one div at the same time, so to have multiple animations simultaneously. Here's an example from an existing game: http://www.cram.com/flashcards/games/stellar-speller/gre-hit-parade-group-1-320949 Please don't use jQuery, just normal Javascript. Thank you!
<!DOCTYPE html>
<html>
<head>
<style>
#table{
height:300px;
width:900px;
background:red;
}
#ball {
width: 25px;
height: 25px;
position: absolute;
background-color: white;
border-radius:50px;
display:none;
z-index:11;
}
</style>
<script> function animation(){ var elem =
document.getElementById("ball");
document.getElementById("ball").style.display ='block';
document.getElementById("ball").disabled = true;
var x = event.clientX;
var y = event.clientY;
var id = setInterval(frame, 2);
function frame() {
if (x == 900) {
clearInterval(id);
}
else {
x++;
elem.style.top = y + 'px';
elem.style.left = x + 'px';
}
}
}
</script>
</head>
<body>
<div id="table" onclick="animation()">
<div id="ball"></div>
</body>
</html>
few issues in your code. First you have to clone or create a new ball each time you click (be aware that cloning an element won't copy attached events). Don't mix HTML and Javascript and use addEventListener, which let you have event as argument
document.getElementById('table').addEventListener("click",animation);
function animation(event){
var ball = document.getElementById("ball");
//clone element or create another one (you should set a class in you css)
var elem = ball.cloneNode(true);
document.getElementById("table").appendChild(elem);
elem.style.display ='block';
// document.getElementById("ball").disabled = true;
var x = event.clientX;
var y = event.clientY;
var id = setInterval(frame, 2);
function frame() {
if (x == 900) {
clearInterval(id);
}
else
{
x++;
elem.style.top = y + 'px';
elem.style.left = x + 'px';
}
}
}
#table{
height:300px;
width:900px;
background:red;
}
#ball {
width: 25px;
height: 25px;
position: absolute;
background-color: white;
border-radius:50px;
display:none;
z-index:11;
}
<div id="table">
<div id="ball"></div>
</div>
You're reusing the same #ball element for every animation, it can't be in two places at once. To create a new element each time, use document.createElement. Then you'll have to place that element somewhere with appendChild, and remove it with removeChild when the animation's finished.
And some smaller things:
If you will have more than one ball in the document, use a class instead of an ID - IDs are only supposed to be used once.
Your <div>s aren't closed with </div> in the HTML, which is probably Firefox's problem. Do people still use Opera?
You should use requestAnimationFrame here instead of setInterval.
And you should try to use addEventListener instead of onclick.
Good luck!
If you have an element whose height is animating using a CSS transition, is there a way to use jQuery or pure Javascript to get its finished height before the transition completes?
Consider the following example: https://jsfiddle.net/qm6zz0kq/
<div id="test"></div>
<style>
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 100px;
}
</style>
<script>
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
setInterval(function() {
testElement.innerHTML = 'Height: ' + testElement.clientHeight;
}, 100);
</script>
How could you modify the interval so it always generates "Height: 100"?
I've considered doing some kind of jQuery clone that doesn't have to transition and measuring its height but in this instance, the CSS is nested enough that I'd have to clone basically of the element's parents to make sure it's correct and that could be expensive.
You can put another hidden div (hidden-test, as an example) that is the same as the div test and add to it the class showing right away (without timeout), then get its height, that will be the same.
Look here an example: https://jsfiddle.net/qm6zz0kq/1/
You could read the actual CSSRule, note though this would just get the value defined in the CSS. For instance if the height was specified as 70% it would give 70% and not the actual px height it would end up as, eg if parents height was 170px, it wouldn't give you the value of 70% of 170px. Also note this will not work if the stylesheet is include from a file <link href="css.css">
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
setTimeout(function() {
var rule = getRule("#test.showing");
if(rule){
testElement.innerHTML = 'Height: ' + rule.style.height;
}
}, 100);
function getRule(selector) {
var foundRule = null;
[].slice.call(window.document.styleSheets)
.filter(sheet=>sheet.rules || sheet.cssRules).forEach(sheet=>{
foundRule = foundRule || [].slice.call(sheet.rules||sheet.cssRules)
.filter(rule=>rule.selectorText == selector);
});
if(foundRule && foundRule[0]) return foundRule[0];
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 100px;
}
<div id="test"></div>
You could also put in an element that is a clone. You do not have to also clone the parents like you mention in your question. You just have to insert the element into the same parent. This particular example uses display:none to hide the element, the returned value will not be a calculated value. Again like above if the parent's height is 400px and the height of the element is 75%, 100px will not be returned, 75% would be.
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
//display:none so we do not have to see the temp element
clone.style.display = "none";
clone.classList.add("showing");
testElement.parentNode.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
//Clone the element
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
//display:none so we do not have to see the temp element
clone.style.display = "none";
clone.classList.add("showing");
testElement.parentNode.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
//Remove it as we dont need it anymore
clone.remove();
setTimeout(function() {
testElement.innerHTML = 'Height: ' + endHeight;
}, 300);
#parent {
height:300px;
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 70%;
}
<div id="parent">
<div id="test"></div>
</div>
If you want the actual calculated height you would need to change the clone to use a couple different stles.
visibility:hidden to hide it instead of display:none as display will make it so we won't get a calculated value.
position:absolute to prevent it from modifying the parents dimensions
clone.style.visibility = "hidden";
clone.style.position = "absolute";
//needed to make sure element is contained by parent
parent.style.position = parent.style.position || "relative";
var endHeight = window.getComputedStyle(clone).height;
var testElement = document.getElementById('test');
setTimeout(function() {
testElement.className = 'showing';
}, 100);
//Clone the element
var clone = testElement.cloneNode();
//remove transition so we can get end height
clone.style.transition = "none";
clone.style.visibility = "hidden";
clone.style.position = "absolute";
clone.classList.add("showing");
var parent = testElement.parentNode;
parent.style.position = parent.style.position || "relative";
parent.appendChild(clone);
var endHeight = window.getComputedStyle(clone).height;
//Remove it as we dont need it anymore
clone.remove();
setTimeout(function() {
testElement.innerHTML = 'Height: ' + endHeight;
}, 300);
#parent {
height:300px;
}
#test {
width: 100px;
height: 0;
transition: height 2s ease-in-out;
background: #F00;
}
#test.showing {
height: 70%;
}
<div id="parent">
<div id="test"></div>
</div>
You can add an 'animationend' event listener to the element .
Example :
testElement.addEventListener('animationend' , showNewHeight);
showNewHeight function(){
// show new height ...do something after animation .
}
Source : http://www.w3schools.com/jsref/event_animationend.asp
hope this helps..
Here I have a function that creates a div box, but I wish to move the styling to a external css file. When I do that, however, the boxes are no longer styled. How could I apply the css to the div boxes that are created dynamically? Is it even worth moving the styling to an external css file?
function createBox() {
var box = document.createElement("DIV");
box.setAttribute("id", "box");
document.body.appendChild(box);
boxStyle = box.style;
boxStyle.position = "absolute";
boxStyle.background = '#'+Math.floor(Math.random()*16777215).toString(16);
boxStyle.width = Math.floor(Math.random()*50) + '%';
boxStyle.height = Math.floor(Math.random()*50) + '%';
boxStyle.top = Math.floor(Math.random()*100) + '%';
boxStyle.left = Math.floor(Math.random()*100) + '%';
}
EDIT: I realized that what's been causing me grief in my CSS is that I was using Math.floor and Math.random() functions that don't work in CSS. Silly me!
As you are creating div with id #box, add the following css in the external css file and include it in the <head>
#box {
position: absolute;
background: red;
width: 90px;
height: 50px;
left: 100;
top: 100;
}
It looks like you will creating more boxes of with same id #box. I would recommend you to create based on classes. And use css to apply the styles based on the classname.
function createBox() {
var box = document.createElement("DIV");
box.setAttribute("class", "boxes");
document.body.appendChild(box);
}
css:
.boxes {
position: absolute;
background: red;
width: 90px;
height: 50px;
left: 100;
top: 100;
}
demo
You can apply CSS to dynamicly created items the same way for usual HTML. All you need is to target the correct selector.
http://codepen.io/anon/pen/mnIhj
You can gather the properties you wish to apply on the div box in a class in external style sheet, and use:
function createBox() {
var box = document.createElement("DIV");
box.setAttribute("id", "box");
box.className = "boxStyle";
document.body.appendChild(box);
}
Try this chnages in your script,
function createBox() {
var box = document.createElement("DIV");
box.setAttribute("id", "box");
document.body.appendChild(box);
var boxStyle = document.getElementById("box").style;
boxStyle.position = "absolute";
boxStyle.background = '#'+Math.floor(Math.random()*16777215).toString(16);
boxStyle.width = Math.floor(Math.random()*50) + '%';
boxStyle.height = Math.floor(Math.random()*50) + '%';
boxStyle.top = Math.floor(Math.random()*100) + '%';
boxStyle.left = Math.floor(Math.random()*100) + '%';
}