Optimizing Interactive SVG JavaScript Animation - javascript

I am trying to animate SVG's in a web browser using JavaScript. My current methodology is using innerHTML:
var e = new entity();
function draw() {
element = document.getElementById("canvas");
e.radius += .1;
e.pos[0] += .1; e.pos[1] += .1;
var insides = "";
insides += '<svg height="80%" viewBox="0 0 100 100">' + e.show() + '</svg>';
element.innerHTML = insides;
}
function entity() {
this.pos = [0, 0];
this.radius = 1;
this.show = function() {
return '<circle cx="' + String(this.pos[0]) + '" cy="' + String(this.pos[1]) + '" r="' + String(this.radius) + '" />';
}
}
window.setInterval(draw, 60);
<div id="canvas" style="text-align:center;"></div>
I want to make sure I am not wasting too many resources, so are there any ways that are less resource-intensive in the HTML document/JavaScript to do controlled interactive animations with SVG's such as this?

Recreating the whole SVG every time is extremely inefficient. Just update the geometry attributes.
var e = new entity();
function draw() {
element = document.getElementById("canvas");
e.radius += .1;
e.pos[0] += .1; e.pos[1] += .1;
e.show();
}
function entity() {
this.element = document.getElementById("mycircle");
this.pos = [0, 0];
this.radius = 1;
this.show = function() {
this.element.cx.baseVal.value = this.pos[0];
this.element.cy.baseVal.value = this.pos[1];
this.element.r.baseVal.value = this.radius;
}
}
window.setInterval(draw, 60);
<div id="canvas" style="text-align:center;">
<svg height="80%" viewBox="0 0 100 100">
<circle id="mycircle" cx="0" cy="0" r="0" />
</svg>
</div>

Related

display percentage arc in javascript [duplicate]

This question already has answers here:
Circular percent progress bar
(5 answers)
Closed 5 years ago.
how to display percentage the form a circle or arc in JavaScript.
I want to display the black color circumference in percentage. Say If I input max value 20 and min value 10, it should display 50% of a circle (arc)
How to do it?.
<!DOCTYPE html>
<html>
<body>
//displays circle with dimensions
<svg height="300" width="300">
<circle cx="150" cy="100" r="90" stroke="brown" stroke-width="10" fill="white"/>
</svg>
<br/><br/>
maxValue: <input type="text" value="" id="value1" /><br/>
minValue: <input type="text" value="" id="value2" /><br/>
<input type="button" value="Stroke-percentage" onclick="" />
stroke-percentage = maxValue/minValue * 100
</body>
</html>
Santho's answer is correct for SVG, but i would like to mention HTML5's Canvas element as an alternative:
/**
* arcPercentage
*
* #param {{ radius?: number, rate?: number, color?: string }} parameters
* #returns
*/
function arcPercentage(parameters) {
var radius = (parameters.radius !== void 0 ? parameters.radius : 100);
var rate = (parameters.rate !== void 0 ? parameters.rate : 1);
var color = (parameters.color !== void 0 ? parameters.color : "rgba(255,0,0,1)");
var c = document.createElement("canvas");
var size = c.width = c.height = radius * 2;
var ctx = c.getContext("2d");
if (rate == 0) {
return c;
}
ctx.fillStyle = color;
ctx.beginPath();
//Start in origo
ctx.arc(radius, radius, 0, 0, 0);
//Move to start position
ctx.arc(radius, radius, radius, 0, 0);
//Arc to percentage
ctx.arc(radius, radius, radius, 0, (Math.PI * 2) * rate);
//move to origo
ctx.arc(radius, radius, 0, (Math.PI * 2) * rate, (Math.PI * 2) * rate);
ctx.fill();
ctx.closePath();
return c;
}
//TEST
//Get nodes
var inputNode = document.getElementById("circle-input");
var imageNode = document.getElementById("circle-image");
//Bind event
inputNode.onchange = inputNode.onkeyup = inputNode.onmouseup = function() {
//Only fire if valid input
if (inputNode.validity.valid) {
//Parse value
var value = parseInt(inputNode.value, 10) / 100;
//Draw the arc
imageNode.src = arcPercentage({
color: "blue",
radius: 100,
rate: value
}).toDataURL();
}
};
<input id="circle-input" min="0" max="100" type="number" value="0">
<br/>
<img id="circle-image">
HTML code:
<!DOCTYPE html>
<html>
<body>
<svg id="svg" width="200" height="200" viewport="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle r="90" cx="100" cy="100" fill="transparent" stroke-width="1em" stroke-dasharray="565.48" stroke-dashoffset="0" stroke="#666"></circle>
<circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="297.48" stroke-dashoffset="0" stroke="red" stroke-width="1em"></circle>
</svg>
Value: <input type="text" value="" id="value1" /><br/>
<input type="button" value="Stroke-percentage" id="generateProgress" />
</body>
</html>
Jquery script:
$('#generateProgress').on('click', function(){
var val = parseInt($('#value1').val());
var $circle = $('#svg #bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = $circle.attr('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
$circle.css({ strokeDashoffset: pct});
$('#cont').attr('data-pct',val);
}
});
HTML code:
<!DOCTYPE html>
<html>
<body>
<svg id="svg" width="200" height="200" viewport="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle r="90" cx="100" cy="100" fill="transparent" stroke-width="1em" stroke-dasharray="565.48" stroke-dashoffset="0" stroke="#666"></circle>
<circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="297.48" stroke-dashoffset="0" stroke="red" stroke-width="1em"></circle>
</svg>
Value: <input type="text" value="" id="value1" /><br/>
<input type="button" value="Stroke-percentage" onclick="generateProgress()" />
</body>
</html>
Jquery script:
$('#generateProgress').on('click', function(){
var val = parseInt($('#value1').val());
var $circle = $('#svg #bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = $circle.attr('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
$circle.css({ strokeDashoffset: pct});
$('#cont').attr('data-pct',val);
}
});
Javascript code:
var value = document.getElementById("value1");
function generateProgress() {
var val = parseInt(value);
var circle = document.getElementById('bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = circle.getAttribute('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
circle.style.strokeDashoffset = pct;
}
}

Partial Arc around an image with html / css / js / jquery / angularjs

I would like to have a partial arc around an image, and as the time goes the arc is getting smaller and smaller:
Any ideas?
Thanks
Update
After seeing in the comments that people insist for introducing what effort I've made by myself in order to try to achieve that here's the code I wrote, but it was not the way I wanted it to be:
HTML:
<div style="position:relative;">
<img style="position:absolute; width:100px; height:100px; border-radius:50%;left:50px;top:50px;" src="http://iv1.lisimg.com/image/522888/408full-adriana-lima.jpg" />
<canvas id="myCanvas" style="background-color:red;" width="500" height="500"></canvas>
JS:
var imgSurrounder = function () {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.lineWidth = 30;
ctx.strokeStyle = "#000";
ctx.stroke();
ctx.lineWidth = 40;
var j = 0;
var i = 0;
var num = 50;
var s = 0;
var step = 2 * Math.PI / num;
var e = (i + 1) * step;
var drawStroke = function () {
//console.log(j++);
ctx.beginPath();
ctx.arc(100, 100, 50, s, e);
ctx.strokeStyle = "#fff";
ctx.stroke();
i++;
if (i === num) {
return;
}
s = e - 0.5 * step;
e = (i + 1) * step;
setTimeout(drawStroke, 1000);
}
setTimeout(drawStroke, 10);
}
},
The results was:
But you can see that it's not so clean an neat.
In addition, if such profile images are spread closely to each other the rectangular canvas is not so convenient to draw in. I was feeling that I'm not doing it in the right way and that there was a better way.
But I don't know if I really should write all this stuff in the question because I'm afraid that people won't read it because it's too long and then I'll be stuck with no answer as I a few questions.
Therefore I felt that it isn't relevant at all to show all my trials here in order to not make my question dirty and keep it clean and focused.
Again, if those are the rules her so of course I respect it.
Thanks anyway
Ok, I found this one to be the exact thing I was looking for:
HTML:
<div class="container">
<div id="activeBorder" class="active-border">
<div id="circle" class="circle">
<span id="startDeg" class="0"></span>
</div>
</div>
JS:
var draw = function (prec) {
$(function drawSector() {
var activeBorder = $("#activeBorder");
if (prec > 100)
prec = 100;
var deg = prec * 3.6;
if (deg <= 180) {
activeBorder.css('background-image', 'linear-gradient(' + (90 + deg) + 'deg, transparent 50%, #A2ECFB 50%),linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
}
else {
activeBorder.css('background-image', 'linear-gradient(' + (deg - 90) + 'deg, transparent 50%, #39B4CC 50%),linear-gradient(90deg, #A2ECFB 50%, transparent 50%)');
}
var startDeg = $("#startDeg").attr("class");
activeBorder.css('transform', 'rotate(' + startDeg + 'deg)');
$("#circle").css('transform', 'rotate(' + (-startDeg) + 'deg)');
});
}
var count = 0;
setInterval(function () {
draw(count++);
}, 100);
Reference:
http://jsfiddle.net/wun8T/1/

HTML5 Canvas lines with increase margin

I'm playing with HTML5 Canvas and Javascript and find some interesting logic of drawing the lines. Maybe you can help me.
I want to draw lines with increased width, but when the width is too wide for a margin and couple lines becomes one, I want to add some margin of 1px. This happens after the i becomes 14
function draw5() {
var ctx = document.getElementById('tutorial').getContext('2d');
var increaseHorizontal = 0;
for(var i = 0; i < 17; i++) {
var lineWidthVar = 1 + i;
if(i >= 14){
increaseHorizontal += 1;
}
ctx.lineWidth = lineWidthVar;
ctx.beginPath();
ctx.moveTo(5 + i * (14 + increaseHorizontal), 5);
ctx.lineTo(5 + i * (14 + increaseHorizontal), 140);
ctx.stroke();
}
}
draw5();
After I add the increaseHorizontal variable, which is 1,2,3, the drawing of line gets extra pixels to the x axis, after the 14th line, this is how it looks:
It seems strange, because when I hardcode 15 instead of (14 + increaseHorizontal), drawing looks nicer for the 15th line, but with overall increased value of x axis throughout the all lines.
I hope it is clear what I am saying, and I hope it is just a small mistake I am not thinking of.
Here is the whole code sample:
function draw5() {
var ctx = document.getElementById('tutorial').getContext('2d');
var increaseHorizontal = 0;
for(var i = 0; i < 17; i++) {
var lineWidthVar = 1 + i;
if(i >= 14){
increaseHorizontal += 1;
}
ctx.lineWidth = lineWidthVar;
ctx.beginPath();
ctx.moveTo(5 + i * (14 + increaseHorizontal), 5);
ctx.lineTo(5 + i * (14 + increaseHorizontal), 140);
ctx.stroke();
}
}
draw5();
canvas {
border: 2px solid #000;
}
<canvas id="tutorial" width="300" height="150"></canvas>
Link to the jsfiddle
You could keep track of the new start position and use that.
function draw5() {
var ctx = document.getElementById('tutorial').getContext('2d');
var startPosition = 0;
var spacer = 5;
for (var i = 0; i < 17; i++) {
var lineWidthVar = 1 + i;
startPosition = startPosition + lineWidthVar + spacer;
ctx.lineWidth = lineWidthVar;
ctx.beginPath();
ctx.moveTo(startPosition, 5);
ctx.lineTo(startPosition, 140);
ctx.stroke();
}
}
draw5();
canvas {
border: 2px solid #000;
}
<canvas id="tutorial" width="300" height="150"></canvas>

How would I create a trail behind my shapes in canvas?

Right now I have some shapes (I only included the triangle as they're all generated the same way) that are generated when the user spins the mouse wheel. I want to leave a trail behind the shapes that slowly disappear. I've looked around and tried a few different ways but I can't seem to get any of them to work. Here's the code:
<html>
<head>
<script>
var canvas;
var context;
var triangles = [];
var timer;
function init() {
canvas = document.getElementById('canvas');
context = canvas.getContext("2d");
resizeCanvas();
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
canvas.onwheel = function(event) {
handleClick(event.clientX, event.clientY);
};
var timer = setInterval(resizeCanvas, 30);
}
function Triangle(x,y,triangleColor) {
this.x = x;
this.y = y;
this.triangleColor = triangleColor;
this.vx = Math.random() * 30 - 15;
this.vy = Math.random() * 30 - 15;
this.time = 100;
}
function handleClick(x,y) {
var colors = [[0,170,255], [230,180,125], [50,205,130]];
var triangleColor = colors[Math.floor(Math.random()*colors.length)];
triangles.push(new Triangle(x,y,triangleColor));
for (var i=0; i<triangles.length; i++) {
drawTriangle(triangles[i]);
}
}
function drawTriangle(triangle) {
context.beginPath();
context.moveTo(triangle.x,triangle.y);
context.lineTo(triangle.x+25,triangle.y+25);
context.lineTo(triangle.x+25,triangle.y-25);
var c = triangle.triangleColor
context.fillStyle = 'rgba(' + c[0] + ', ' + c[1] + ', ' + c[2] + ', ' + (triangle.time / 100) + ')';
context.fill();
}
function resizeCanvas() {
canvas.width = window.innerWidth-20;
canvas.height = window.innerHeight-20;
fillBackgroundColor();
for (var i=0; i<triangles.length; i++) {
var t = triangles[i];
drawTriangle(t);
if (t.x + t.vx > canvas.width || t.x + t.vx < 0)
t.vx = -t.vx
if (t.y + t.vy > canvas.height || t.y + t.vy < 0)
t.vy = -t.vy
if (t.time === 0) {
triangles.splice(i,1);
}
t.time -= 1;
t.x += t.vx;
t.y += t.vy;
}
}
function fillBackgroundColor() {
context.fillStyle = "black";
context.fillRect(0,0,canvas.width,canvas.height);
}
window.onload = init;
</script>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
</html>
The way I managed to do this is by storing past locations of my objects (for example 10 past locations, and draw line from one to the next one.
1.Add arrays pastX and pastY to your triangle object.
this.pastX = [];
this.pastY = [];
2.Each tick put every element 1 place further and add current location as first.
Right before you update triangle positions
for(var k = t.pastX.length; k > 0; k--){
if(k < 10){
t.pastX[k] = pastX[k-1];
t.pastY[k] = pastY[k-1];
}
t.pastX[0] = t.x;
t.pastY[0] = t.y;
draw lines between them like this (from 0 to 1, from 1 to 2 and so on);
for(var k = 0; k < t.pastX.length - 1; k++){
//draw line from pastX[k], pastY[k] to pastX[k + 1], pastY[k + 1]
}
Hope I helped you.

How to use requestAnimationFrame in mousemove event?

In my SVG based web application, a user can select a large number of shapes (even 800 or more) & move them about on the screen, which as one can imagine severely impacts the framerate. After reading the merits of requestAnimationFrame, I have been working since yesterday trying to incorporate it into my mousemove function, with the hope that by using a combination of throttling & requestAnimationFrame, I can get to a smooth framerate.
Here is the jsFiddle in which I am moving 600 svg shapes on mousemove. Ignoring throttling for now, how can I incorporate requestAnimationFrame into the mousemove function.
http://jsfiddle.net/rehankhalid/5t5pX/
HTML CODE
<svg width="1200" height="800">
<symbol>
<g id="symbol1">
<path fill="#3ab6d1" d="M27.7,1.1C16-2,3.9,5.1,0.8,16.9s4,23.8,15.7,26.9c11.7,3.1,23.8-4,26.9-15.7C46.6,16.3,39.6,4.2,27.7,1.1z M38.3,26.9C36,35.7,26.9,41,18,38.7C9.1,36.4,3.8,27.3,6.1,18.5C8.4,9.6,17.5,4.3,26.4,6.6C35.3,9,40.6,18,38.3,26.9z"/>
<g fill="#d5e1db">
<path d="m25.8,.8c2.5,.4 4.8,1.2 7,2.5l-2.9,4.6c-1.5-.8-3.1-1.4-4.8-1.7l.7-5.4z"/>
<path d="m13.9,2.2l1.7,5.1c-1.5,.7-3,1.5-4.3,2.7l-3.8-3.9c.9-.8 1.9-1.5 2.9-2.2 1.2-.7 2.3-1.3 3.5-1.7z"/>
</g>
<path fill="#196275" d="m26.4,38.5l1.8,5.2c-2.5,.7-4.9,.9-7.4,.8l.6-5.4c1.6,0 3.3-.2 5-.6z"/>
<path fill="#42aec2" d="M6.1,18.4C8.4,9.5,17.5,4.2,26.4,6.5S40.7,18,38.4,26.8C36,35.7,26.9,41,18,38.7C9.1,36.3,3.7,27.2,6.1,18.4 z"/>
<path fill="#d2dfdd" d="m28.7,25.9l.5-4.9 9.5-1.3c0,.3 .1,.6 .2,.8 .4,2.9 0,5.8-1,8.3l-9.2-2.9z"/>
<path fill="#1b6273" d="m19.7,29.3h4.9l2.1,9.4c-.3,.1-.6,.2-.8,.2-2.9,.6-5.7,.5-8.4-.3l2.2-9.3z"/>
<path fill="#368098" d="m15.9,25.8l3.6,3.4-4.8,8.3c-.3-.1-.5-.3-.8-.4-2.6-1.5-4.6-3.5-6-5.9l8-5.4z"/>
<path fill="#d2dfdd" d="m18.6,16.5l-3,3.9-8.7-4c.1-.3 .2-.5 .3-.8 1.2-2.7 3.1-4.9 5.3-6.5l6.1,7.4z"/>
<path fill="#bde2e9" d="m26.4,32.1l-8.4-.5-4.9-6.8 2.3-8 7.7-3.2 7.3,4 1.5,8.2-5.5,6.3z"/>
</g>
</symbol>
<g id="default">
<g id="my-element">
</g>
</g>
<g id="draggroup">
</g>
</svg>
Javascript
document.addEventListener('mousedown', mousedown, false);
var element = document.getElementById('my-element');
var defaultg = document.getElementById('default');
var mainsvg = document.getElementsByTagName('svg')[0];
var draggroup = document.getElementById('draggroup');
var svgns = "http://www.w3.org/2000/svg";
var x = 0, y = 0;
var scale = '0.25';
for (var i = 0; i < 600; i++) {
var useelement = document.createElementNS(svgns, "use");
useelement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#symbol1');
useelement.setAttribute('transform', 'translate(' + x + ',' + y + ') scale(' + scale + ')');
x += 12;
if (x === 240) {
x = 0;
y += 12;
}
element.appendChild(useelement);
}
var bbox = element.getBBox();
var selectionRect = document.createElementNS(svgns, "rect");
selectionRect.setAttribute('id', 'selectionrect');
selectionRect.setAttribute('x', bbox.x);
selectionRect.setAttribute('y', bbox.y);
selectionRect.setAttribute('width', bbox.width);
selectionRect.setAttribute('height', bbox.height);
selectionRect.setAttribute('fill', 'grey');
selectionRect.setAttribute('opacity', '0.2');
draggroup.appendChild(selectionRect);
var dx = 0, dy = 0, mx = 0, my = 0;
var rectdx = 0, rectdy = 0;
var elementdx = 0, elementdy = 0;
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
var svgXY = getSvgCordinates(event);
mx = svgXY.x; // mouse down x
my = svgXY.y;// mouse down y
draggroup.appendChild(element);
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
}
}
function mouseup() {
document.removeEventListener('mousemove', mousemove, false);
document.removeEventListener('mouseup', mouseup, false);
draggroup.setAttribute('transform', 'translate(0,0)');
rectdx += dx;
rectdy += dy;
elementdx += dx;
elementdy += dy;
selectionRect.setAttribute('transform', 'translate(' + rectdx + ',' + rectdy + ')');
element.setAttribute('transform', 'translate(' + elementdx + ',' + elementdy + ')');
defaultg.appendChild(element);
dx = 0;
dy = 0;
}
function mousemove(event) {
var svgXY = getSvgCordinates(event);
dx = svgXY.x - mx;
dy = svgXY.y - my;
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
Basically, you'd need to move the draggroup.setAttribute() calls to another function. Since you only need to animate when the mouse is down, add another variable to indicate whether you're dragging or not, and only call requestAnimationFrame when that's the case.
var isDragging = false;
function update() { // New function
if (isDragging) {
requestAnimationFrame(update); // Call self again, if still dragging
}
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
isDragging = true;
requestAnimationFrame(update); // Start animation loop
// existing logic here...
}
}
function mouseup() {
isDragging = false;
// existing logic here...
}
You're already storing the updated location for the group (dx, dy) in a separate variable, so remove the draggroup.setAttribute() call from mousemove() and add the above modifications, and you should be good!

Categories