I am creating a scene using A-frame (https://aframe.io) and I'm currently using an animation in my scene. I'm wondering how I can randomize the delay on my animation so the animation delays from a random amount from 0 to 2 seconds. How can this be done? My current code:
<html>
<head>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<!--Animte the box with a ranom delay from 0 - 2 seconds. -->
<a-box position="-1 1.6 -5" animation="property: position; delay: 1000; to: 1 8 -10; dur: 2000; easing: linear; loop: true" color="tomato"></a-box>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
I currently have the delay set to 1000 (1 second since the delay is in milliseconds). What should happen instead is the delay should be a random amount from 0 - 2000 since 2000 milliseconds is 2 seconds. How can this be done?
Random delay only at the start - first cycle
Register the a-box component with a custom one that will do the math and apply it to the animation on the a-box <a-box position="-1 1.6 -5" random-delay animation color="tomato"></a-box>.
Building the custom aframe component:
AFRAME.registerComponent('random-delay', {
init: function (el) {
// this.el points to the element the component is placed on
var el = this.el;
}
});
Set your min and max values
AFRAME.registerComponent('random-delay', {
init: function (el) {
var el = this.el;
var min = 0;
var max = 2000;
}
});
Math.floor(Math.random() * (max - min + 1)) + min returns an integer random number between min and max and than we build the string for the animation:
AFRAME.registerComponent('random-delay', {
init: function (el) {
var el = this.el;
var min = 0;
var max = 2000;
// generates a random number between the set min and max
var delay = Math.floor(Math.random() * (max - min + 1)) + min;
var animation = `property: position; delay: ${delay}; from: -1 1.6 -5; to: 1 8 -10; dur: 2000; easing: linear; loop: true`
}
});
Apply the animation string with the delay generated with .setAttribue():
AFRAME.registerComponent('random-delay', {
init: function (el) {
var el = this.el;
var min = 0;
var max = 2000;
// generates a random number between the set min and max
var delay = Math.floor(Math.random() * (max - min + 1)) + min;
// builds the string for the animation
var animation = `property: position; delay: ${delay}; to: 1 8 -10; dur: 2000; easing: linear; loop: true`
// updating the animation component with the .setAttribute() function
el.setAttribute('animation', animation)
}
});
Random delay on every animation cycle
place these lines of code into a new function:
function setAnimation(min, max) {
// generates a random number between the set min and max
var delay = Math.floor(Math.random() * (max - min + 1)) + min;
var animation = `property: position; delay: ${delay}; from: -1 1.6 -5; to: 1 8 -10; dur: 2000; easing: linear; `
console.log(delay);
// updating the animation component with the .setAttribute function
el.setAttribute('animation', animation)
}
At every animationcomplete event set a new animation with the function we created
function setAnimation(min, max) {
// generates a random number between the set min and max
var delay = Math.floor(Math.random() * (max - min + 1)) + min;
var animation = `property: position; delay: ${delay}; from: -1 1.6 -5; to: 1 8 -10; dur: 2000; easing: linear; `
console.log(delay);
// updating the animation component with the .setAttribute function
el.setAttribute('animation', animation)
}
The result should look something like this:
<html>
<head>
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
</head>
<script>
AFRAME.registerComponent('random-delay', {
init: function (el) {
// this.el points to the element the component is placed on
var el = this.el;
let min = 0;
let max = 2000;
// initial animation
setAnimation(min, max)
function setAnimation(min, max) {
// generates a random number between the set min and max
let delay = Math.floor(Math.random() * (max - min + 1)) + min;
let animation = `property: position; delay: ${delay}; from: -1 1.6 -5; to: 1 8 -10; dur: 2000; easing: linear; `
console.log(delay);
// updating the animation component with the .setAttribute function
el.setAttribute('animation', animation)
}
el.addEventListener('animationcomplete', () => {
setAnimation(min, max)
});
}
});
</script>
<body>
<a-scene>
<a-box position="-1 1.6 -5" random-delay animation autoplay color="tomato">
</a-box>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
Related
I've been trying to update properties of the camera on the fly to no avail - really don't want to setup multiple cameras and switch between them.
AFRAME.registerComponent("camera-controller", {
init: function() {
this.el.addEventListener('model-loaded', this.update.bind(this));
},
update: function() {
var data = this.data;
var el = this.el;
// el.fov = 75;
el.object3D.el.components.camera.data.fov = 20;
el.updateProjectionMatrix();
console.log(el.object3D.el.components.camera.data.fov);
}
});
camera.fov just returns undefined and updateProjectionMatrix() is not a function.
You can simply update the [camera]s FOV property via setAttribute("camera", "fov", newFOV). Something like this:
<script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("foo", {
init: function() {
// grab the camera
const cam = document.querySelector("[camera]")
// on click
document.body.addEventListener("click", e => {
// grab the current FOV
const fov = cam.getAttribute("camera").fov
// update the camera with the new FOV
cam.setAttribute("camera", "fov", fov == 80 ? 120 : 80)
})
}
})
</script>
<a-scene foo>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
You can make the transition animated with the standard animation component (animating camera.fov) - here's an example with one animation (to utilize the above js logic, you could make two animations if you prefer a more 'declarative' style).
I have been asked, using Konvajs, to work out an animation that will rotate a circle as if spinning on its central x-axis. So imagine a coin spinning on a table. The intention is to reveal some text on the circle. At the start the circle is fully visible as if from behind so no text visible, then it flips to reveal the text.
I have this code that does a rotation like a spinning wheel.
Can anyone give me a tween / animation approach that would achieve the spinning coin effect?
// the tween has to be created after the node has been added to the layer
var tween = new Konva.Tween({
node: group,
duration: 4,
rotation: 360,
easing: Konva.Easings.BackEaseOut
}
});
tween.play();
After some research it looks like a 3D spin requires heavier lifting which may not be available or work well on mobile.
A good second-best appears to be using scaleX and animating from 0 > 1.
group.scaleX(0);
var tween = new Konva.Tween({
node: group,
duration: .25,
scaleX: 1,
easing: Konva.Easings.EaseOut
});
Here is an example of the second-best version using scaleX() effect. Because of the need to calculate scaleX() and control visibility of the text so as to make it appear that the disc is solid, I moved away from a tween and over to an animation().
// Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container1', width: 300, height: 200});
// Add a layer for line
var layer = new Konva.Layer({draggable: false});
s1.add(layer);
// just a plain JS object to keep common variables in hand.
var cfg = { w: 300, h: 200, r: 80, txtSize: 520};
var group = new Konva.Group();
var circle = new Konva.Circle({x: cfg.w/2, y: cfg.h/2, radius: cfg.r, fill: 'DodgerBlue', stroke: 'DeepPink', strokeWidth: 5})
group.add(circle)
var textValue = new Konva.Text({
id: "t1",
x: cfg.w/2,
y: cfg.h/2,
text: '',
fill: 'DeepPink ',
fontSize: cfg.txtSize
});
group.add(textValue);
textValue.offset({x: textValue.getWidth()/2, y: textValue.getHeight()/2});
layer.add(group)
// to spin a group about a point, set the offset to that point, then set the x & y to that point to !
var pos = group.getClientRect();
RotatePoint(group, {x: pos.x + pos.width/2, y: pos.y + pos.height/2});
// Everything is ready so draw the canvas objects set up so far.
s1.draw()
$('#st').on('click', function(){
group.scaleX(1);
var txt = $('#theText').val();
setValue(txt);
})
// set the offset for rotation to the given location and re-position the shape
function RotatePoint(shape, pos){ // where pos = {x: xpos, y: yPos}
var initialPos = shape.getAbsolutePosition();
var moveBy = {x: pos.x - initialPos.x, y: pos.y - initialPos.y};
// offset is relative to initial x,y of shape, so deduct x,y.
shape.offsetX(moveBy.x);
shape.offsetY(moveBy.y);
shape.x(initialPos.x + moveBy.x);
shape.y(initialPos.y + moveBy.y);
}
var setValue = function(newText){
// work out scaling to make text fit into the circle
var txt = this.layer.find('#t1')[0];
txt.text(newText).scale({x:1, y: 1})
var txtSize = txt.getClientRect();
var maxW = (cfg.r); // max allowed width of text
var txtScaleW = (txtSize.width > maxW ? ( maxW / txtSize.width) : 1);
var maxH = cfg.r; // max allowed height of text
var txtScaleH = (txtSize.height > maxH ? ( maxH / txtSize.height) : 1);
// finally decide which is the worst case and use that scaling
var txtScale = ( txtScaleW > txtScaleH ? txtScaleH : txtScaleW);
txt.scale({x: txtScale, y: txtScale});
txt.offset({x: txt.getWidth()/2, y: txt.getHeight()/2});
layer.draw()
}
// set initial text & spin !
setValue('BBB');
var anim, pos = 0, frameCnt = 0
if (anim) {anim.stop(); }
anim = new Konva.Animation(function(frame) {
frameCnt = frameCnt + 1;
if (frameCnt % 2 === 0){
pos = pos + .2
var scaleX = Math.sin(pos)
textValue.visible(scaleX < 0 ? false : true);
group.scaleX(scaleX);
if (pos % 360 === 0){ console.log('spin') }
}
}, layer);
anim.start();
div
{
float: left;
margin: 0 5px;
}
p
{
margin: 0 5px 5px 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<div id='container1' style="width: 300px, height: 200px;"></div>
<div>
<p> <input type='text' id='theText' value='BBB' /> <button id='st'>Change text</button> </p>
</div>
How to implement smooth scrolling like on this website?
https://boredomdoctors.com/banjo-dog/
I've found on website similar solution:
if (window.addEventListener) window.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel = document.onmousewheel = wheel;
function wheel(event) {
var delta = 0;
if (event.wheelDelta) delta = event.wheelDelta / 120;
else if (event.detail) delta = -event.detail / 2;
handle(delta);
if (event.preventDefault) event.preventDefault();
event.returnValue = false;
}
function handle(delta) {
var time = 900; // delay time
var distance = 140; // delta point
// Dom where it will apply
$('html, body').stop().animate({
scrollTop: $(window).scrollTop() - (distance * delta)
}, time, 'easeOutExpo' );
}
I have problem with resume animation after stop it. Any suggestion ? I using kineticjs to make element run along the path, after i reach the end, the animation stop for 2 second then start again.
here my code :
var stage = new Kinetic.Stage({
container: 'canvas',
height: 484,
width: 478
});
var layer = new Kinetic.Layer();
stage.add(layer);
var img = new Image();
img.src = 'images/ani/bullet_blue.png';
var circle = new Image();
circle.src = 'images/ani/1.png';
var shapeCircle = new Kinetic.Image({
x: 10,
y: 10,
image: circle,
width: circle.width,
height: circle.height,
offset: {
x: 0,
y: 0
}
});
layer.add(shapeCircle);
layer.draw();
function animation(points, shape, duration, loop, callback) {
layer.add(shape);
window.anim = new Kinetic.Animation(function (frame) {
var time = frame.time, timeDiff = frame.timeDiff, frameRate = frame.frameRate;
var percent = time / duration, scale = 0.5, opacity = 0;
if (percent < 0.05 || percent > 0.95) opacity = 0;
else opacity = 1;
// scale calculate
if (percent < 0.5) {
scale += percent
}
else if (percent > 0.5) {
scale = 1 - (percent - 0.5)
}
if (percent > 1) {
anim.stop();
percent = 0;
setTimeout(function () { anim.start(); }, 500);
} else {
var pos = Math.ceil(percent * points.length);
pos = pos > (points.length - 1) ? (points.length - 1) : pos;
if (pos == points.length - 1) anim.stop();
shape.setScale(scale, scale);
shape.setOpacity(opacity);
shape.setPosition(points[pos].x, points[pos].y);
}
}, layer);
anim.start();
}
animation(points1, new Kinetic.Image({
x: points1[0].x,
y: points1[0].y,
image: img,
width: img.width,
height: img.height,
opacity: 0,
scaleX: 0.5,
scaleY: 0.5,
offset: {
x: 15,
y: 30
}
}), 2000);
If you want a repeating animation, you might consider a tween instead.
When a tween finishes, it fires a finished event. In that finished handler, you can:
Reset the tween to the starting position using tween.reset,
Start a timer to wait 2 seconds using setTimer,
When the timer fires, restart the tween using tween.start.
I am trying to calculate the angle of rotation of a circle, I am using the following script:
var circle = new Kinetic.Circle({
x: 256,
y: 256,
radius: 140,
stroke: 'black',
strokeWidth: 4 ,
offset: [0, 0],
draggable: true,
dragBoundFunc: function (pos) {
var pos = stage.getMousePosition();
var xd = 140 - pos.x;
var yd = 140 - pos.y;
var theta = Math.atan2(yd, xd);
var degree = (theta / (Math.PI / 180) - 45);
this.setRotationDeg(degree);
return {
x: this.getAbsolutePosition().x,
y: this.getAbsolutePosition().y
};
}
});
I don't think it is accurate, I added a shape inside the circle to see the rotation but could not group them together, I would appreciate your suggestions on how to calculate the degree of rotation and how to group the shape with the circle so the rotate at the same time. The complete project script is at http://jsfiddle.net/user373721/Ja6GB. Thanks in advance.
Here is how you calculate the angle of the mouse position from "12 o'clock"
Pretend your canvas is a clock centered in the canvas.
Here's how to calculate the angle of the current mouse position assuming 12 o'clock is zero degrees.
function degreesFromTwelveOclock(cx,cy,mouseX,mouseY){
// calculate the angle(theta)
var theta=Math.atan2(mouseY-centerY,mouseX-centerX);
// be sure theta is positive
if(theta<0){theta += 2*Math.PI};
// convert to degrees and rotate so 0 degrees = 12 o'clock
var degrees=(theta*180/Math.PI+90)%360;
return(degrees);
}
Here is complete code and a Fiddle: http://jsfiddle.net/m1erickson/HKq77/
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var centerX=canvas.width/2;
var centerY=canvas.height/2;
var radius=10;
// draw a center dot for user's reference
ctx.beginPath();
ctx.arc(centerX,centerY, radius, 0 , 2 * Math.PI, false);
ctx.fill();
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
$("#movelog").html("Mouse: "+ mouseX + " / " + mouseY);
$("#angle").html("Angle: "+parseInt(degreesFromTwelveOclock(centerX,centerY,mouseX,mouseY)));
}
function degreesFromTwelveOclock(cx,cy,mouseX,mouseY){
// calculate the angle(theta)
var theta=Math.atan2(mouseY-centerY,mouseX-centerX);
// be sure theta is positive
if(theta<0){theta += 2*Math.PI};
// convert to degrees and rotate so 0 degrees = 12 o'clock
var degrees=(theta*180/Math.PI+90)%360;
return(degrees);
}
$("#canvas").mousemove(function(e){handleMouseMove(e);});
}); // end $(function(){});
</script>
</head>
<body>
<p id="movelog">Move</p>
<p id="angle">Out</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>