I have an increasing value 1 to 10000. I can put this in a sin function to get values like a sin output.
Now I want a linear output, that when I give this value as input it gives me output like this: Actually the values has to be in 0..1 range
0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .8 .7 .6 .5 .4 .3 .2 .1 0
How can I do this fast math way
Long
Actually I want a period of zeros and then a sudden burst to 1 and than a calm down to 0 again. I thought if I get the above version I can put that in an easing function to get this result:
0 0 0 0 0 0 .1 .2 .4 .8 .9 1 .9 .8 .4 .2 .1
A non performant slow way to do this is:
const linear = (() => {
const res = [];
for (let i = 0; i < 100; i++) {
res.push(i / 100);
}
for (let i = 100; i > 0; i--) {
res.push(i / 100);
}
return t => res[Math.round(t) % res.length];
})();
const easing = t => {
return .04 * t / (--t) * Math.sin(25 * t);
};
Use it like easing(linear(t))
I have a text that is wrapping around an SVG circle which is scaling depending on window size – thanks to user enxaneta https://stackoverflow.com/a/56036245/10727821. I want to animate the text so that it would be revolving around the center like a marquee. For this, my code currently looks like this:
function Init(){
let wrap = document.getElementById('wrap');
let thePath = document.getElementById('thePath');
let ellipse = document.getElementById('ellipse');
let w = wrap.clientWidth;
let h = wrap.clientHeight;
ellipse.setAttributeNS(null,"viewBox",`0 0 ${w} ${h}`);
let d = `M${w/10},${h/2}A${4*w/10},${4*h/10} 0 0 0 ${9*w/10} ${5*h/10} A${4*w/10},${4*h/10} 0 0 0 ${w/10} ${5*h/10}`
thePath.setAttributeNS(null,"d", d)
}
window.setTimeout(function() {
Init();
window.addEventListener('resize', Init, false);
}, 15);
let so = 0
function Marquee(){
let tp = document.getElementById('tp');
requestAnimationFrame(Marquee)
tp.setAttributeNS(null,"startOffset",so+"%");
if(so >= 50){so = 0;}
so+= .05
}
Marquee()
<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000" preserveAspectRatio="none">
<path id="thePath" fill="transparent" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500" />
<text stroke="black" font-size="20">
<textPath xlink:href="#thePath" dy="5" id="tp">
Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
</textPath>
</text>
</svg>
</div>
This is working well, except the text gets "swallowed" at the end of the curve (see attached image). I'd like to have it make a full rotation without any interruption. I have tried changing the so variable to a negative value, but this ends up in the text being too far offset so it would only slowly creep onto the page. I was thinking to prepend a text fragment after a certain time, but this wouldn't take into account the startOffset movement and would probably not work…
Thankful for any hints, also those using JS libraries or plugins!
The main idea is that the path has to coil twice. And when the startOffset is at 50% you make it 0. Also because the length of the path is changing when you resize the window you need to recalculate the font-size. I hope it helps.
function Init() {
let w = wrap.clientWidth;
let h = wrap.clientHeight;
ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);
let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
w /
10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
h /
10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
w /
10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;
thePath.setAttributeNS(null, "d", d);
let paths_length = thePath.getTotalLength();
tp.style.fontSize = paths_length / 205;
}
window.setTimeout(function() {
Init();
window.addEventListener("resize", Init, false);
}, 15);
let so = 0;
function Marquee() {
requestAnimationFrame(Marquee);
tp.setAttributeNS(null, "startOffset", so + "%");
if (so >= 50) {
so = 0;
}
so += 0.05;
}
Marquee();
#wrap{width:100vw; height:100vh}
svg {
background:#eee;
}
<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">
<path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A400,400 0 0 0 100 500" />
<text stroke="#000000" >
<textPath xlink:href="#thePath" dy="5" id="tp">
Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •
</textPath>
</text>
</svg>
</div>
UPDATE
The OP is commenting:
This snippet seems to be working fine for the use case, but when I try to apply it to another font family, the dimensions are off and the two loops start overlapping
One easy fix would be setting the attribute textLength equal to the length of the path divided by 2 (since the path is coiling twice - is twice as long as it should be). Also you need tu use lengthAdjust="spacingAndGlyphs" that is controlling how the text is stretched or compressed into that length.
function Init() {
let w = wrap.clientWidth;
let h = wrap.clientHeight;
ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);
let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
w /
10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
h /
10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
w /
10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;
thePath.setAttributeNS(null, "d", d);
let path_length = thePath.getTotalLength();
//////////////////////////////////////////////////
tp.setAttributeNS(null,"textLength",path_length/2)
//////////////////////////////////////////////////
tp.style.fontSize = path_length / 200;
}
window.setTimeout(function() {
Init();
window.addEventListener("resize", Init, false);
}, 15);
let so = 0;
function Marquee() {
requestAnimationFrame(Marquee);
tp.setAttributeNS(null, "startOffset", so + "%");
if (so >= 50) {
so = 0;
}
so += 0.05;
}
Marquee();
#wrap{width:100vw; height:100vh}
svg {
background:#eee;
font-family:consolas;
}
<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">
<path id="thePath" fill="gold" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500 A400,400 0 0 0 900 500 A400,400 0 0 0 100 500" />
<text stroke="#000000" >
<textPath xlink:href="#thePath" id="tp" lengthAdjust="spacingAndGlyphs">Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • </textPath>
</text>
</svg>
</div>
You may also need to add/remove some Coming Soon • if the text becomes too stretched / squished.
UPDATE 2
Apparently the last solution do not work on Firefox. This is another solution to this problem.
Initially I'm setting the font size much bigger than needed. Next I check if the text length is bigger than half path length, and if so I'm reducing the font size. I'm doing this in a while loop:
function Init() {
let w = wrap.clientWidth;
let h = wrap.clientHeight;
ellipse.setAttributeNS(null, "viewBox", `0 0 ${w} ${h}`);
let d = `M${w / 10},${h / 2}A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 *
w /
10} ${5 * h / 10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${w / 10} ${5 *
h /
10} A${4 * w / 10},${4 * h / 10} 0 0 0 ${9 * w / 10} ${5 * h / 10} A${4 *
w /
10},${4 * h / 10} 0 0 0 ${w / 10} ${5 * h / 10}`;
thePath.setAttributeNS(null, "d", d);
let path_length = thePath.getTotalLength();
//begin at a bigger size than needed
let font_size = 100;
ellipse.style.fontSize = font_size+"px";
// while the text length is bigger than half path length
while(tp.getComputedTextLength() > path_length / 2 ){
//reduce the font size
font_size -=.25;
//reset the font size
ellipse.style.fontSize = font_size+"px";
}
}
window.setTimeout(function() {
Init();
window.addEventListener("resize", Init, false);
}, 15);
let so = 0;
function Marquee() {
requestAnimationFrame(Marquee);
tp.setAttributeNS(null, "startOffset", so + "%");
if (so >= 50) {
so = 0;
}
so += 0.02;
}
Marquee();
html, body {
margin: 0;
height: 100%;
width: 100%;
}
body {
font-family: "Arimo", sans-serif;
}
#wrap{
width:100%;
height:100%;
position: fixed;
top: 0;
left: 0;
}
text {
text-transform: uppercase;
font-weight: lighter;
}
<div id="wrap">
<svg id="ellipse" version="1.1" viewBox="0 0 1000 1000">
<path id="thePath" fill="transparent" d="M100,500A400,400 0 0 0 900 500 A400,400 0 0 0 100 500" />
<text stroke="black">
<textPath xlink:href="#thePath" dy="5" id="tp" lengthAdjust="spacingAndGlyphs">Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon • Coming Soon •</textPath>
</text>
</svg>
</div>
I'm trying to add a text (that is a countdown ) made with JS that works, but only after another text (made with JS) has finished it's animation, but I'm struggling with it.
I've tried some methods but nothing worked for me, I'm new but I'm a quick learner.
Can someone help me out?
Here is a part of my code:
HTML
// Set the date we're counting down to
var countDownDate = new Date("Feb 1, 2019 11:59:20").getTime();
// Update the count down every 1 second
var x = setInterval(function() {
// Get todays date and time
var now = new Date().getTime();
// Find the distance between now and the count down date
var distance = countDownDate - now;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Display the result in the element with id="countdown"
document.getElementById("countdown").innerHTML = days + " days left " + hours + "h "
+ minutes + "m " + seconds + "s ";
// Add text to <h1 id="fade">
var comingSoon = document.getElementById("fade");
comingSoon.innerHTML = 'COMING SOON';
// If the count down is finished, write some text
if (distance < 0) {
clearInterval(x);
//document.getElementById("countdown").innerHTML = "Well.. what are you waiting for?";
// If countdown finished, remove the <p id="countdown">
var parent = document.getElementById("comingSoonContainer");
var child = document.getElementById("countdown");
parent.removeChild(child);
// Create new <p> with text
var para = document.createElement("p");
var node = document.createTextNode("New Paragraph works fine.");
para.appendChild(node);
var element = document.getElementById("comingSoonContainer");
element.appendChild(para);
// Replace <h1> with text
para = document.createElement("h1");
node = document.createTextNode("Enjoooooy !!");
para.appendChild(node);
parent = document.getElementById("comingSoonContainer");
child = document.getElementById("fade");
parent.replaceChild(para, child);
//document.getElementById("fade").innerHTML = "Enjoy !!";
}
}, 1000);
#countdown {
font-family: 'Raleway', sans-serif;
font-size: /*1.3em;*/ 3em;
color: #ffffff;
/*including fade animation*/
-webkit-animation-name: fade;
-webkit-animation-duration: 12s;
animation-name: fade;
animation-duration: 12s;
}
/* Fade animation */
#fade {
-webkit-animation-name: fade;
-webkit-animation-duration: 8s;
animation-name: fade;
animation-duration: 8s;
}
#-webkit-keyframes fade {
from {opacity: .0}
to {opacity: 1}
}
#keyframes fade {
from {opacity: .0}
to {opacity: 1}
}
<div id="comingSoonContainer">
<h1 id="fade"></h1>
<p id="countdown"></p>
</div>
Thanks
UPDATE:
First, the problem is with your CSS styles for the countdown paragraph . You set the paragraph color to white and since your body is white as well, that's why you never see the countdown. But if you change the color to black for instance, you'll see your countdown.
Second, I added an animationend event to your fade element and put the setInterval inside it.
Here is the CSS you need to change:
#countdown {
font-family: 'Raleway', sans-serif;
font-size: /*1.3em;*/ 3em;
color: #000; // Color was #ffffff
/*including fade animation*/
-webkit-animation-name: fade;
-webkit-animation-duration: 12s;
animation-name: fade;
animation-duration: 12s;
}
And here is the javascript:
// Set the date we're counting down to
var countDownDate = new Date("Feb 1, 2019 11:59:20").getTime();
// Add text to <h1 id="fade">
var comingSoon = document.getElementById("fade");
comingSoon.innerHTML = 'COMING SOON';
comingSoon.addEventListener('animationend', ()=>{
// Update the count down every 1 second
var x = setInterval(function() {
// Get todays date and time
var now = new Date().getTime();
// Find the distance between now and the count down date
var distance = countDownDate - now;
// Time calculations for days, hours, minutes and seconds
var days = Math.floor(distance / (1000 * 60 * 60 * 24));
var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Display the result in the element with id="countdown"
document.getElementById("countdown").innerHTML = days + " days left " + hours + "h "
+ minutes + "m " + seconds + "s ";
});
});
I removed the part where you check for distance just because it had nothing to do with what you asked for help. You can add it though.
But here is the working fiddle
https://jsfiddle.net/fwxL1sj4/33/
There is animation callbacks:
function callFunction(){
}
var element=document.getElementById('fade');
element.addEventListener("webkitAnimationEnd", callfunction,false);
element.addEventListener("animationend", callfunction,false);
element.addEventListener("onaimationend", callfunction,false);
I'm in a brain freeze here.
I have 2 times:
(int) 1815 (18:15) and (int) 1915 (19:15) and I want to calculate the amount of 15 minute blocks between them. (4). How can I approach this in a solid manner?
You could take the minutes of every value and get the delta divided by a quarter hour.
function getMin(t) {
return Math.floor(t / 100) * 60 + t % 100;
}
var a = 1815,
b = 1915,
delta = Math.round((getMin(b) - getMin(a)) / 15);
console.log(delta);
This question already has answers here:
Recording FPS in webGL
(5 answers)
Closed 5 years ago.
I am trying to display the frames per second on my html canvas. I dont mind where its placed on the canvas for now as I can tweak it at later period. Here what I have so far;
var updateAnimation = function () {
requestAnimFrame(updateAnimation);
var anim = global.animation;
var e = global.events;
//Set current time
anim.animationCurrentTime = Date.now();
//Set start time
if (anim.animationStartTime === undefined) {
anim.animationStartTime = anim.animationCurrentTime;
}
//Clear the animationStage
webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
//Draw scene
drawScene();
//Set previous time as current time to use in next frame
anim.animationLastTime = anim.animationCurrentTime;
}
global.document.animationStage = document.getElementById("animation-scene");
webgl = setupScene(global.document.animationStage);
setupShaders();
setupAllBuffers();
setupEvents();
setupLight();
setupTextures();
initScene();
}
<header>
<h1 style="text-align: center">Applied Computer Graphics and Vision</h1>
<p>Instructions<span>
<br />
<br />
Rotation - Click and drag in the direction of rotation <br />
Increase/Decrease Orbit Radius - Up and Down Keys <br />
Increase/Decrease Orbit Speed - Left and Right Keys <br />
Translation Of X - Shift plus mouse drag <br />
Translation Of Y - Alt plus mouse drag <br />
Translation Of Z - Mouse scroll
</span></p>
</header>
<canvas style="float:left" ; id="animation-scene"></canvas>
<canvas id="myCanvas" width="1400" height="800"></canvas>
<script>
/* Sets */
var area = document.getElementById('animation-scene');
area.setAttribute('height', window.innerHeight);
area.setAttribute('width', window.innerWidth);
</script>
</body>
</html>
Any help or advice would be great. I know the basic idea of having to count the number of frames rendered and once one second has passed store that in the fps variable but not sure on how to implement this through my update animation function.
I also have methods that sets the current/start time for the scene within the update animation function.
Displaying FPSs is pretty simple and has really nothing to do with WebGL other than it's common to want to know. Here's a small FPS display
const fpsElem = document.querySelector("#fps");
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<div>fps: <span id="fps"></span></div>
You should probably not use Date.now() for computing FPS as Date.now() only returns milliseconds. requestAnimationFrame already gets passed the time in microseconds since the page loaded.
Also you don't really "place it on the canvas". Just use another HTML element separate from the canvas. If you want them to overlap then use CSS to make them overlap
const gl = document.querySelector("#c").getContext("webgl");
const fpsElem = document.querySelector("#fps");
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then; // compute time since last frame
then = now; // remember time for next frame
const fps = 1 / deltaTime; // compute frames per second
fpsElem.textContent = fps.toFixed(1); // update fps display
drawScene(now);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function drawScene(time) {
gl.disable(gl.SCISSOR_TEST);
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const halfWidth = gl.canvas.width / 2;
const halfHeight = gl.canvas.height / 2
const x = halfWidth - f(time) * halfWidth;
const y = halfHeight - f(time * 1.17) * halfHeight;
const w = (halfWidth - x) * 2;
const h = (halfHeight - y ) * 2;
gl.scissor(x, y, w, h);
gl.enable(gl.SCISSOR_TEST);
gl.clearColor(f(time * 1.1), f(time * 1.3), f(time * 1.2), 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function f(v) {
return Math.sin(v) * .5 + .5;
}
#container {
position: relative; /* needed so child elements use this as their base */
}
#hud {
position: absolute;
left: 5px;
top: 5px;
background: rgba(0, 0, 0, 0.5); /* 50% opaque black */
color: white;
padding: .5em;
font-family: monospace;
border-radius: .5em;
}
<div id="container">
<canvas id="c"></canvas>
<div id="hud">fps: <span id="fps"></span></div>
</div>