The calculation behind progress circle using dasharray and dashoffset - css

I'm building a small angularjs directive which will display a progress circle(I don't want animation) and there will be a text in the middle of it indicating the percentage of completion.
The html code of the circle is:
<path fill="none" stroke="rgb(0,51,117)" stroke-width="5" stroke-linecap="square" d="M25,2.5A22.5,22.5 0 1 1 2.5,25A22.5,22.5 0 0 1 25,2.5" stroke-dasharray="105" stroke-dashoffset="{{circle.percentage*(-140)/100 + 105 }}">
</path>
I don't know the calculation behind the dasharray and dashoffset, I get the calculation {{circle.percentage*(-140)/100 + 105 }} by adjusting the dashoffset and guessing.
I have a fiddle http://jsfiddle.net/ADukg/10992/
As you can see, it only works for the circles from 30% to 70%. Does anyone know the correct calculation of it? I use the CSS tag as one of the tags of my question because the calculation should also work in CSS. Thank you in advance

The radius of your circle is 22.5, so the correct length for your dash array should be
(2 * PI * 22.5) = 141.37
Secondly, you can just use stroke-dasharray by itself. No need to use stroke-dashoffset also.
stroke-dasharray="{{circle.percentage*142/100}} 142"
Updated fiddle
Note: I removed the stroke-linecap="square". You may want to put it back if you added that deliberately.

You could have two circles that overlay each other. The first circle being the gray outline and the second circle being the progress overlay. Then change the stroke-dasharray and stroke-dashoffset values of just the second circle and rotate the svg by 90 degrees:
html:
<circle cx="25" cy="25" r="22.5" stroke="rgb(188,188,188)" stroke-width="5" fill="none"></circle>
<circle cx="25" cy="25" r="22.5" stroke="rgb(0,51,117)" stroke-width="5" fill="none" stroke-dasharray="{{circle.circumference}}" stroke-dashoffset="{{circle.circumference * (1 - circle.percentage/100)}}"></circle>
js:
function MyCtrl($scope) {
var radius = 22.5;
var circumference = Math.PI*(radius*2);
$scope.circles = [];
for(var i=0; i<=10; i++){
var circle = {
percentage : i* 10,
circumference: circumference
};
$scope.circles.push(circle);
}
}
css:
.progress {
transform: rotate(-90deg);
}
Link to jsfiddle
I found this tutorial helpful.

Related

Animating SVGs Via CSS

I found this immensely helpful CodePen about animating SVGs via CSS. First question, would it also work with line elements? I tried it with a few different keywords but didn't succeed:
svg {
width: 200px;
height: 200px;
}
.path path {
transition: d 400ms;
}
.line line {
transition: x1 400ms, y1 400ms, x2 400ms, y2 400ms;
}
.path:hover path {
d: path("M10,90 L90,10");
}
.line:hover line {
x1: line("10");
y1: point("90");
x2: coord("90");
y1: pos("10");
}
<svg class="path" viewBox="0 0 100 100">
<g stroke="black" stroke-width="5">
<path d="M10,10 L90,90"/>
</g>
</svg>
<svg class="line" viewBox="0 0 100 100">
<g stroke="black" stroke-width="5">
<line x1="10" y1="10", x2="90" y2="90"/>
</g>
</svg>
Also, where is the documentation for this, the fact that you have to write d: path("M10,90 L90,10"); instead of just d: "M10,90 L90,10"; seems non-trivial to me, how would that work with other properties of other elements?
Only presentation attributes have their counter-parts in CSS.
As of today, here is the list of these attributes:
Properties with a presentation attribute
Elements that support the presentation attribute
cx, cy
‘circle’ and ‘ellipse’
height, width, x, y
‘foreignObject’, ‘image’, ‘rect’, ‘svg’, ‘symbol’, and ‘use’
r
‘circle’
rx, ry
‘ellipse’ and ‘rect’
d
‘path’
fill
Any element in the SVG namespace except for animation elements, which have a different ‘fill’ attribute.
transform
For historical reasons, the transform property gets represented by different presentation attributes depending on the SVG element it applies to:transformAny element in the SVG namespace with the exception of the ‘pattern’, ‘linearGradient’ and ‘radialGradient’ elements.‘patternTransform’‘pattern’. ‘patternTransform’ gets mapped to the transform CSS property[css-transforms-1].‘gradientTransform’‘linearGradient’ and ‘radialGradient’ elements.‘gradientTransform’ gets mapped to the transform CSS property [css-transforms-1].
alignment-baseline, baseline-shift, clip-path, clip-rule, color, color-interpolation, color-interpolation-filters, cursor, direction, display, dominant-baseline, fill-opacity, fill-rule, filter, flood-color, flood-opacity, font-family, font-size, font-size-adjust, font-stretch, font-style, font-variant, font-weight, glyph-orientation-horizontal, glyph-orientation-vertical, image-rendering, letter-spacing, lighting-color, marker-end, marker-mid, marker-start, mask, mask-type, opacity, overflow, paint-order, pointer-events, shape-rendering, stop-color, stop-opacity, stroke, stroke-dasharray, stroke-dashoffset, stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-opacity, stroke-width, text-anchor, text-decoration, text-overflow, text-rendering, transform-origin, unicode-bidi, vector-effect, visibility, white-space, word-spacing, writing-mode
Any element in the SVG namespace.
You can see that lines's x1, x2, y1 and y2 aren't there, and thus can't be controlled and thus animated by CSS.
For the values d can receive in CSS it's described here and basically it can receive either a path() function, or the value none.

svg handwriting effect ,Can I clip the stroke path to a png image instead of text?

first of all, Im new to this webpage and Im really sorry for any mistakes.
So, I needed to animate a png image of a Calligraphy text just like a handwriting effect.
Recently I've watched a youtube video https://www.youtube.com/watch?v=wab7lQKHXL4, its about making a handwriting animation with strokes clipped to texts.
So I was wondering, if I could clip the strokes to a png image and animate it just like the handwriting animation.
I tried to import the png image inside inkspace and drawn path on top of it, gave suitable stroke-width saved it as a svg file, and tried to animate it with stroke-dasharray, but its not working ,its not even showing up.
although I can do the same with written texts just like in the video.
So is there any way ,I can achieve the same goal using a png image.
thanks in advance, and sorry if I don't make any sense
The video you are linking is astonishing, if not crooked. The commentary points out it is done with a "custom" version of Inkscape, and I have to say there are some UI elements I have never seen. (Admittedly, I am on an older version from 2021, but the video is from 2017, when Inkscape oficially wasn't even on verson 1.0.) Maybe the specialists on https://graphicdesign.stackexchange.com/ can tell you more about this "custom" version?
The core action is selecting from the menu bar Object -> Set Clip. If this is the same as selecting Object -> Clip -> Set in my version (1.02), this cannot work. This action sets a clip-path. Clip paths only clip to their fill area and have no stroke properties.
What you have to do instead is
set the fill to none and the stroke color to white (#ffffff)
select a Object -> Mask -> Set
Also, even after watching for multiple times, I am not sure on which element the onload attribute is set. Setting it on the path element in the mask certainly won't work, there is no load event happening there.
The logical choice would be to wait for the image loading.
The result should be something like this:
document.querySelector('image#letter').addEventListener('load', function () {
setTimeout(() => document.querySelector('#stroke .dashed').style.strokeDashoffset = 0, 200)
})
.dashed {
fill: none;
stroke: white;
stroke-width: 8px;
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: stroke-dashoffset 1s linear;
}
image#letter {
mask: url(#stroke);
}
<svg viewBox="0 0 100 100" width="100" height="100">
<mask id="stroke" maskUnits="userSpaceOnUse">
<path class="dashed" d="M 61.739,30.223 C 61.739,30.223 55.369,26.515 51.839,25.606 48.819,24.825 45.469,24.022 42.489,24.964 40.379,25.63 38.329,27.161 37.349,29.137 35.899,32.044 35.099,36.236 36.989,38.878 36.989,38.878 46.459,41.753 50.839,42.679 54.399,43.431 57.729,48.053 56.429,52.972 55.599,56.117 51.469,57.478 48.379,58.502 45.569,59.433 42.389,59.495 39.509,58.837 36.089,58.056 30.199,53.913 30.199,53.913" />
</mask>
<image id="letter" width="100" height="100"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA
GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAwxJREFUeJzt2zuIHWUYBuAnm2hi
NAnBoCJE8FIERTQRBRErgyIGSysRL6CtYqOgTUhh4xJILAUNYqFdai0sRBSUxAvEJsRLlIAXMOom
XlaL/wjx7Jxkz+ycmdlz3gcGln+Z4Z39ds4/881/iIiIiIiIiIiIiIiIiIiIiIgY19quA9SwFlux
afDz2W7jNGtN1wEuYB0ewD24HTcphTjXIk7iOL7ER4Pti8HvogHr8QK+wz81t1N4Dfe1G3363IrP
1S/E8PZeu/FXZl3XAYbsxLvKHHE+v+APbMBlkw41q7YqHzNV/+XH8Lwyj2wc2u9i7MDDOIivreIr
pE8OWlqIP/GM8a7kNUrhXsFpKUgtm3DG0oI8ssLjbsGeFR5jJj1klU/GTZnrOsDAbRVjb7eeogf6
UpCrKsaOt56iB/pSkKpb175ka1VfTvqnirEbW0/RA30pyImKscdxUcs5YuBO1Q+Eb+LSDnPNrDmj
+1ff4DnlaTxatEdpl5+vUXgSb+Ap7JKPtIl70Xid3DN4Hy/hfqVtHw17FL+p12r/AQdwfduhp93V
SnNwQb3CnMXLSns+GrQZj+EwfjZ+YT5U3QXorb6/Uz/XHG7GHcqEvkt5oXWhif1j3K1cbTFhG3Ev
XsWvRl8p+7sKOMuuwCHVBVlQ5qXowD7VRXm6y1CzbA6fWlqQw12GWq6+NBebtIjXK8ZvaDtIHdNY
EDhSMbat9RQ1TGtBfq8Y+6v1FDVMa0Gq7qhOtZ6ihmktyO6KsRNth1jNHlRWtjfhWmWB3PBd1pMN
HX8mzONvvKW0Q+q6UpnQq9r0q6qn1bV5//8DHsWzuGaZ+2/AE0Z/fWFfw3knpi/NxXllDW+Vb/EB
PsOPyjuPRWX56XbcoswZm0fsfwR3qb7zihGGr5Cmtk9weYvnMTV24x3lWaGJQixgLy5p8ySm0Tbl
hdQhfGX8QhxT3ssvd+7pnb7MIaNsV1Yw7sB1yjzx35c+Tw+275X55aiyZCgiIiIiIiIiIiIiIiIi
IiIiIiIiIiIiIiIiIiIiIiIm51/fJjIk1BlBegAAAABJRU5ErkJggg==
"
</svg>
Once you have a d-path, you can offload the work to a Native Web Component <draw-path>
disclaimer: The last drawn path is from Jake Archibald his 2013 blog: Animated Line Drawing SVG
window.customElements.define("draw-path", class extends HTMLElement {
constructor() {
let template = (id) => document.getElementById(id).content.cloneNode(true);
super() // super sets and returns this scope
.attachShadow({mode: "open"}) // sets and returns this.shadowRoot
.append(template(this.nodeName));
this.line = this.shadowRoot.querySelector("#line");
this.line.setAttribute("d", this.getAttribute("d") || "m10 60c30-70 55-70 75 0s55 70 85 0");
this.line.setAttribute("stroke", this.getAttribute("stroke") || "black");
this.line.setAttribute("stroke-width", this.getAttribute("stroke-width") || "2");
this.pen = this.shadowRoot.querySelector("#pen");
this.onmouseover = (evt) => this.draw();
}
connectedCallback() {
this.shadowRoot.querySelector("svg").setAttribute("viewBox",this.getAttribute("viewBox")||"0 0 180 150");
this.draw();
}
showpen(state = true, scale) {
this.pen.style.display = state ? 'initial' : 'none';
}
draw() {
clearInterval(this.drawing);
this.showpen();
this.dashoffset = 1;
this.pathlength = this.line.getTotalLength();
this.drawing = setInterval(() => this.update(), 50);
}
update() {
this.dashoffset -= this.getAttribute("speed") || 0.02;
let {x,y} = this.line.getPointAtLength(this.pathlength - this.dashoffset * this.pathlength);
this.pen.setAttribute("transform", `translate(${x-2} ${y-2})`);
this.line.style.strokeDashoffset = this.dashoffset;
if (this.dashoffset <= 0) this.end();
}
end() {
clearInterval(this.drawing);
this.showpen(false);
//console.log("end",this.line);
clearTimeout(this.timeout);
this.timeout = setTimeout(()=>this.draw(),2000);
}
});
<draw-path viewBox="20 20 60 60" d='M 61.739,30.223 C 61.739,30.223 55.369,26.515 51.839,25.606 48.819,24.825 45.469,24.022 42.489,24.964 40.379,25.63 38.329,27.161 37.349,29.137 35.899,32.044 35.099,36.236 36.989,38.878 36.989,38.878 46.459,41.753 50.839,42.679 54.399,43.431 57.729,48.053 56.429,52.972 55.599,56.117 51.469,57.478 48.379,58.502 45.569,59.433 42.389,59.495 39.509,58.837 36.089,58.056 30.199,53.913 30.199,53.913'></draw-path>
<draw-path stroke='red' stroke-width='8' speed=".01"></draw-path>
<draw-path stroke="deeppink" viewBox="0 0 400 370" d="M11.6 269s-19.7-42.4 6.06-68.2 48.5-6.06 59.1 12.1l-3.03 28.8 209-227s45.5-21.2 60.6 1.52c15.2 22.7-3.03 47-3.03 47l-225 229s33.1-12 48.5 7.58c50 63.6-50 97-62.1 37.9"></draw-path>
<template id="DRAW-PATH">
<style>
:host { display: inline-block }
svg { width: 180px; height: 130px; background: beige }
</style>
<svg xmlns="http://www.w3.org/2000/svg">
<path id='line' pathlength='1' stroke-dasharray='1' stroke-dashoffse='1' fill='transparent'/>
<path id='pen' stroke='black' stroke-width='2' fill='gold' d='m12 19l7-7l3 3l-7 7l-3-3zm6-6l-2-8l-14-3l4 15l7 1l5-5zm-16-11l8 8m-1 1a2 2 0 104 0a2 2 0 10-4 0'/>
</svg>
</template>
Note:
Be aware M or m (moves) in paths create a new stroke, drawn at the same time, not sequentially.
So stroke-dash* settings are applied concurrent.
That is why in all blogs you only see single stroke simple paths or polylines used.

Animating SVG Path using CSS instead of <animateMotion>

I am trying to place an arrow on the midpoint of the bezier curve. The solution using <animateMotion> in the question How properly shift arrow head on cubic bezier in SVG to its center , which moves a <path> which is the arrow and freezes it at the middle of the bezier curve, works only in Firefox. As the curve's points keep changing frequently in my case, I didn't want to use marker-mid as it is costly for me to calculate the midpoint of the bezier curve everytime.
<svg viewBox="0 0 500 500">
<g>
<path id="path1" d="M291.698 268.340 C321.698 268.340, 411.904 93.133 441.904 93.133"></path>
<path class="path_arrow" d="M0,0 L6,6 L0,12" transform="translate(-3,-6)">
<animateMotion dur="0s" rotate="auto" fill="freeze" keyTimes="0;1" keyPoints="0.5;0.5">
<mpath xlink:href="#path1"></mpath>
</animateMotion>
</path>
</g>
<g transform="translate(166.698,243.340)">
<circle r="5" class="p1"></circle>
</g>
<g transform="translate(441.904,68.133)" >
<circle r="5" class="p2"></circle>
</g>
</svg>
Is there any way to do this using CSS Animations so as to avoid using <animateMotion> ?
EDIT 1:
The endpoints of the curve here is draggable and so the points of the curve tend to change frequently. The animation is to move the arrow to the center of the curve without calculating the midpoints.
EDIT 2:
Thanks to Kaiido's comment, I added calcMode="linear" and the arrow is now placed on the path as expected. But When I reposition the end point by dragging, the arrow stays in its initial position(as shown) in Chrome but it is expected to move along the parent path. In Firefox this is working fine as before.
You could achieve the same with CSS offset-path, offset-distance and offset-rotate properties:
#path1 {
fill: none;
stroke: black;
}
.path_arrow {
transform: translate( -3px, -6px );
offset-path: path("M220 104C220 144,400 180,400 224");
offset-rotate: auto;
offset-distance: 50%;
}
body { background: white; }
<svg width="500" height="500" >
<path id="path1" d="M 220 104 C 220 144 400 180 400 224"
fill="none" stroke-width="2" stroke="black" />
<path class="path_arrow" d="M0,0 L0,12 L12,6 z" />
</svg>
But their browser support is far lower than the one of SMIL, so I wouldn't recommend it.
Note that I did fix the answer there where they were missing a calcMode="linear" attribute to make Blink browsers happy.
If you need IE support, you may want to try this js implementation which seems to support <animateMotion> and rotate, keeping in mind I didn't test it myself.
Regarding the question's "EDIT 2":
Chrome indeed seems to need an explicit call to update the <mpath>. This can be done by calling the beginElement() method of the <animationMotion> element after each update:
document.querySelector('svg').onmousemove = function(e) {
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
path1.setAttribute( 'd', `M ${x} ${y} C 220 144 400 180 400 224` );
// Chrome requires an explicit update
document.querySelector('animateMotion').beginElement();
}
<pre style="position: absolute;pointer-events:none">move your mouse to change the path</pre>
<svg width="500" height="500" >
<path id="path1" d="M 220 104 C 220 144 400 180 400 224"
fill="none" stroke-width="2" stroke="black" />
<path d="M0,0 L0,12 L12,6 z" transform="translate(-3,-6)">
<animateMotion dur="0s" rotate="auto" fill="freeze"
keyTimes="0;1" keyPoints="0.5;0.5" calcMode="linear" >
<mpath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#path1"/>
</animateMotion>
</path>
</svg>
You have to use webkit animation inside the css. Also i would recommend https://codepen.io/collection/yivpx/. Is an open source project for SVG optimization. I also encourage you to name all your classes within your SVG in order to do cool CSS stuff with animate, like:
animation: kaboom 5s ease alternate infinite;

Complex SVG clip-path responsive

I'm trying to take a complex path shape and apply it as a clip-path mask in css, but I can't figure out how to get the clip mask to "fill" the parent container.
Rather it just gets cut off or doesn't expand to fill the available space.
If I add clipPathUnits="objectBoundingBox" it doesn't appear at all.
<svg viewBox="0 0 720 720">
<defs>
<clipPath id="map">
<path d="M568.421 326.842L511.579 270v37.895h-18.947v-18.948h-56.843v37.895l18.948 37.894v18.948h-18.948l37.896 56.842h-37.896l-18.947-18.948v-18.947h-37.895L360 383.684h-18.947l-18.948-37.894v-37.895L360 270l37.895-37.895-18.948-37.895H360v18.948l-18.947-18.948h-18.948v37.895h-37.894l-56.843-18.947-37.894-56.842h-56.842l-18.947-18.948-75.79 75.79v37.895h18.947v75.789L37.895 345.79l5.532 48.163 32.362 46.573 113.685 37.895 94.737 18.947h94.736v-18.947h37.895l18.947 37.895h18.948v56.842l56.842-37.894v-37.896h37.895l18.947-37.894v-37.896l56.842-37.894V345.79l-18.948-18.948z"/><path d="M246.315 194.21h56.843v-18.947l-18.947-37.895h-18.948v37.895h-18.948zM227.368 137.368h18.947v-18.947h-37.894V156.316h18.947zM341.053 175.263h56.842l37.894 37.895-18.947 18.947V270h75.79v-18.947h-37.895v-18.948h37.895V194.21h-37.895l-56.842-56.842h-56.842zM265.263 99.474h18.948v18.947h-18.948zM284.211 61.579h18.947v18.948h-18.947zM303.158 108.947h18.947v18.947h-18.947zM341.053 99.474h37.895v18.947h-37.895zM227.368 80.526h18.947v18.947h-18.947zM378.947 80.526V4.737H360l-37.895 37.894v18.948l18.948 18.947zM587.368 440.526h37.895v37.895h-37.895zM663.158 364.736V345.79h-18.947V402.631l56.842-18.947v-18.948zM378.947 270h18.947v18.947h-18.947zM644.211 421.578h18.947v18.948h-18.947zM644.211 459.474h18.947v18.947h-18.947z"/>
</clipPath>
</defs>
</svg>
https://codepen.io/picard102/pen/aEwJzR
As Robert said, when you specify clipPathUnits="objectBoundingBox", the coordinates in the clip path definition are supposed to be between 0,0 (the top left) and 1,1 (the bottom right).
Your paths are about 700x575, so your path is about 600 to 700 times too big.
The simplest solution is to add a transform attribute to your <clipPath> that scales the coordinates down to the correct range.
<clipPath id="map" clipPathUnits="objectBoundingBox" transform="scale(0.00143, 0.00174)">
1/700 ~= 0.00143
1/575 ~= 0.00174
https://codepen.io/anon/pen/GyvZOM

Calculating CSS transform matrix for SVG animation of rotation during translation

The square should be animated to rotate while moving along a straight line from the red square to the blue square. I can't get this to work as you can see from the moving square never matching the fixed blue square.
That problem appears repeatedly, therefore I need a reusable solution. That is: don't just give specific numbers for the matrix but also the calculation by which you arrived at those values.
So I need a matrix() transform in terms of the following variables:
x1,y1 is the initial point of the straight movement path (in the example below 75,75);
x2,y2 is the final point of the straight movement path (in the example below 175,75);
cx,cy is the center of rotation (in the example below 75,75);
a as angle by which to rotate (in the example below 45deg - I would prefer giving the value in radians but CSS doesn't seem to accept that);
such that the object rotates around cx,cy by angle a while the point x1,y1 moves to x2,y2 along a straight line (note that while x1,y1 and cx,cy coincide in the example below this is not always the case, but the example was supposed to be simple).
Note that I do know how the matrices work, including how to compose transformations. The problem is that I do not know which particular transformations I need for this specific problem.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="250px" height="150px" viewBox="0 0 250 150">
<style>
*{stroke:black;stroke-width:1px;}
#keyframes ani {
0% {fill:#f00;transform:translate(0, 0 );}
/* illustrating that movement works without rotation. */
25% {fill:#00f;transform: translate( 100px, 0 );}
50% {fill:#f00;transform:translate(0, 0 );}
/* with rotation it ends up anywhere but where it is supposed to go. */
75% {fill:#00f;transform: translate( 100px, 0 ) translate( 175px, 75px) rotate( 45deg ) translate( -175px, -75px );}
100% {fill:#f00;transform:translate(0, 0 );}
}
#p {animation: ani 4000ms ease-in-out 10ms infinite normal forwards;}
</style>
<defs>
<path id="def" d="m0,0 l50,0 l0,50 l-50,0 l0,-50" />
</defs>
<use xlink:href="#def" x="50" y="50" style="fill:#faa;" />
<use xlink:href="#def" x="150" y="50" style="fill:#aaf;" transform="rotate( 45 175 75 )" />
<use id="p" xlink:href="#def" x="50" y="50" />
</svg>
The matrix I believe you want is basically this (thank you to this page for helping me figure it out):
transform: matrix(cos(angle),sin(angle),-sin(angle),cos(angle),translateDiffX,translateDiffY);
I don't think you can set trigonometry values in CSS (except perhaps with a pre-processor), so I made this fiddle where the CSS is overwritten via JavaScript (though in a sort of heavy-handed way), and changed up the angles and translation values to test to make sure it worked for other cases.
Note that without the transform-origin at the 0% and 100%, the animation has a sort of arc effect (though perhaps that's desirable/unimportant).

Resources