I just started working with SVG in the last couple days and I've built a simple radial animation for a pomodoro application. I've come to find that the starting point of the animation differs from what I have it set to and see on a desktop browser (0 deg). The below screenshot from my iPhone 6 in Safari shows what I am seeing (same result in Chrome and Firefox as well). You can see this live for yourself here.
The circle's initial start path was at 90 deg so I applied the following to get the desired starting point:
<circle className={className} transform="rotate(-90, 50, 60)">
I've spent the morning searching through posts and the only thing I really found related to what I'm seeing is a comment at the bottom of this answer here. I went to the spec and read up on it, but I'm not really understanding if I am better off drawing the circle using path or this semi-circle idea that was purposed in the comment (which I'm not understanding at all). It's my understanding now that in the mobile browsers however <circle /> is interpreted the initial starting point is different than in a desktop browser (maybe I'm wrong and this is a OS related issue?).
Can anyone explain how to solve this? I'm not looking to have my problem solved by someone, just a nudge in the right direction. I've been reading through posts and googling for the last 2 hours with no luck and not much more understanding of the problem at large. Thank you in advance for your help.
Full Code for the <circle />:
import PropTypes from 'prop-types'
import { styles } from '../../lib'
/**
* #function Circle
* #description renders <circle /> that is conditionally animated.
*
* #prop {String} className
* #prop {Number} duration - desired length of timer formatted to seconds
* #returns React Element
*/
const Circle = ({ className, duration }) => (
<circle className={className} transform="rotate(-90, 50, 60)">
<style jsx>{`
circle {
cx: 50;
cy: 60;
fill: transparent;
r: 35;
stroke-width: 4;
}
.outer {
stroke: ${styles.colors.highLight};
}
.overlay {
stroke: none;
stroke-dasharray: 219; /* NOTE: https://stackoverflow.com/a/33922201/6520579 */
stroke-linecap: round;
}
.cooldownAnimation {
animation: ${duration}s linear normal forwards cooldown;
stroke: ${styles.colors.text};
}
.timerAnimation {
-webkit-animation: ${duration}s linear normal forwards timer;
animation: ${duration}s linear normal forwards timer;
stroke: ${styles.colors.text};
}
#-webkit-keyframes timer {
from {
stroke-dashoffset: 219;
}
to {
stroke-dashoffset: 0;
}
}
#keyframes timer {
from {
stroke-dashoffset: 219;
}
to {
stroke-dashoffset: 0;
}
}
#-webkit-keyframes cooldown {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 219;
}
}
#keyframes cooldown {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 219;
}
}
#media (orientation: landscape) {
circle {
r: 50;
}
.overlay {
stroke-dasharray: 314;
}
#-webkit-keyframes timer {
from {
stroke-dashoffset: 314;
}
to {
stroke-dashoffset: 0;
}
}
#keyframes timer {
from {
stroke-dashoffset: 314;
}
to {
stroke-dashoffset: 0;
}
}
#-webkit-keyframes cooldown {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 314;
}
}
#keyframes cooldown {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 314;
}
}
}
`}</style>
</circle>
)
Circle.propTypes = {
className: PropTypes.string,
duration: PropTypes.number
}
export default Circle
It seems I have fixed the issue and that it was two-fold:
It was in part to how I was writing the CSS. I normally work in Chrome and it didn't cause a fuss over what I was doing; however, after looking in the FireFox dev-tools and seeing that my <circle /> and animation of the stroke was not even visible I found out that...
this code is not valid:
circle {
cx: 50; /* NOT VALID */
cy: 60; /* NOT VALID */
fill: transparent;
r: 35; /* NOT VALID */
stroke-width: 4;
}
These attributes MUST be passed on the element:
<circle
className={className}
cx="50"
cy="60"
r="35"
transform="rotate(-90, 50, 60)"
>
After reading through comments of this article and finding this pen I found out that I did not need to be using stroke-dashoffset and could instead use only stroke-dasharray; but instead of writing in shorthand:
shorthand method:
.overlay {
stroke: none;
stroke-dasharray: 219; /* Set both length of stroke & gap to 219px */
stroke-linecap: round;
}
longhand method:
.overlay {
stroke: none;
stroke-dasharray: 0 219; /* Set stroke length to 0px & gap length to 219px */
stroke-linecap: round;
}
Now in the #keyframes I just had to switch to stroke-dasharray and travel from no stroke length to a length of the entire circumference of the circle and visa versa for the the reverse animation.
#keyframes timer {
from {
stroke-dasharray: 0 219;
}
to {
stroke-dasharray: 219 0;
}
}
In doing this the animation is now functioning as desired across Chrome, FireFox, & Safari on both my iPhone 6 & iPad mini 4. I have no non-Apple devices to test this further unfortunately. I hope this can help out the next guy or gal who runs into this issue, Happy Coding!
Related
I am making a countdown timer circle. The animation works fine on the first iteration, but the circle animation always stays full after the first iteration and does not rest. The number continues to work correctly and rests back to 20, counting down again. I need the red countdown line to copy this.
First time:
Second time:
I have tried adding things like
animation: circletimer 59s linear infinite forwards;
and
animation-iteration-count: infinite
But I can't seem to make the animation happen more than once.
The code that I currently have is:
Countdown component -
interface IProps {
countdown: number
}
const CountDownCircle: FunctionComponent<IProps> = ({
countdown,
}) => {
console.log(countdown)
return (
<div className={'countdown__circle'}>
<svg className={'countdown__circle-svg'} width="200px" height="200px">
<circle className={'circle'} cx="100" cy="100" r="28" />
</svg>
<span className={'timer'}>{countdown}</span>
</div>
)
}
export default CountDownCircle
css(scss) -
.countdown__circle {
position: absolute;
bottom: 34px;
right: 47px;
}
#keyframes circletimer {
0% {
stroke-dashoffset: 500;
stroke-dasharray: 500;
}
100% {
stroke-dashoffset: 0;
stroke-dasharray: 500;
}
}
.countdown__circle-svg {
background-color: transparent;
position: absolute;
background-color: transparent;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotateZ(-90deg);
.circle {
stroke: $red;
stroke-width: 5;
stroke-linecap: round;
fill: transparent;
stroke-dashoffset: 500;
stroke-dasharray: 0;
animation: circletimer 59s linear infinite forwards;
}
}
.timer {
position: absolute;
display: block;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: $black;
font-size: 25px;
font-weight: 100;
font-family: $proximaBold;
}
Any advice on how to make this animation happen infinitely would be helpful.
You must remove forwards from your aniamtion, as forwards indicates that animation stays like it is after the first run.
animation: circletimer 59s linear infinite;
W3Schools description for aniamtion-fill-mode:forwards is: "The element will retain the style values that is set by the last keyframe (depends on animation-direction and animation-iteration-count)"
I currently have an SVG with three circle elements.
Circle 1: Grey border (bottom layer)
Circle 2: Red Dashed border (mid layer)
Circle 3: Blue border (top layer)
I am able to animate the Circle 3 (Blue) to show 40%.
Unfortunately though, Circle 2 (Red) is around the entire circle (100%) instead of 80%.
So my questions are...
Can Circle 2 be animated the same as Circle 3 and occupy only 80%? Or is it not possible because of stroke-dasharray: 0.2em (line 93 in the CSS)?
In the css (line 81 and line 87), I have vector-effect: non-scaling-stroke commented out. I would like to uncomment those two lines but unfortunately the progress circular bar doesn't look correct. Any possible way to make the stroke not responsive while having the desired animation/look?
Any help is appreciated!
Here's the Codepen: https://codepen.io/anon/pen/oaVrpE
P.S.
I'm able to do the animation by adding the "active" class to the circle elements. That class gets added by the buttons. Finally, the "active" class calls the keyframe names in the CSS.
Snippet of the HTML and CSS codes but please do check out the codepen above.
HTML
<svg id="categoryIcon" data-name="category icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 96 96" width="100%" height="100%" class="catIcon userIcon">
<circle class="filler-loader fill-line" cx="48" cy="48" r="47" />
<circle class="filler-loader path path-dashed" cx="48" cy="48" r="47" />
<circle class="filler-loader path path-progress" cx="48" cy="48" r="47" />
<path d="M1536 1399q0 109-62.5 187t-150.5 78H469q-88 0-150.5-78T256 1399q0-85 8.5-160.5t31.5-152 58.5-131 94-89T583 832q131 128 313 128t313-128q76 0 134.5 34.5t94 89 58.5 131 31.5 152 8.5 160.5zm-256-887q0 159-112.5 271.5T896 896 624.5 783.5 512 512t112.5-271.5T896 128t271.5 112.5T1280 512z" transform="translate(26, 25), scale(0.025)"/>
</svg>
CSS
svg.catIcon {
overflow: visible;
display: inline-block;
position: absolute;
top: 0;
left: 0;
.filler-loader {
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.fill-line {
fill: #fff;
stroke: #e3e3e3;
stroke-width: 8px;
// vector-effect: non-scaling-stroke;
}
.path {
fill: none;
stroke-width: 8px;
// vector-effect: non-scaling-stroke;
stroke-dasharray: $dashOffset;
stroke-dashoffset: $dashOffset;
&-dashed {
&.active {
stroke-dasharray: 0.2em;
stroke: red;
}
}
&-progress {
&.active {
stroke: blue;
}
}
}
&.userIcon {
.path-dashed {
&.active {
animation: circProgress80 1.5s cubic-bezier(.6, .09, 0, .94) forwards;
clip-path: url("#circleMask");
}
}
.path-progress {
&.active {
animation: circProgress40 1.5s cubic-bezier(.6, .09, 0, .94) forwards;
}
}
}
}
***Update - solution listed below
I’ve got an svg animation bug I could use some expert help on.
I’m trying to create a similar animation that you can see here (http://springsummer.dk/) when you hover over some of their work (maybe half way down the page) — a line comes in and up and then kind of squishes to the right height.
I recreated this effect in a simple code pen that can be seen here: https://codepen.io/callmegoon/pen/EQRMjG (see code below).
However, in my testing, I came to learn that Safari does not support the negative stroke-dashoffset that I’m using in the animation.
Soooo, I’m trying to figure out a new solution - any ideas? Totally open to a completely different way to accomplish this (maybe a js solution?), but everything I’m doing hasn’t been working. My biggest issue has been the squish part at the end where the back/bottom of the line comes up - I haven’t figured out another way to accomplish that without the negative stroke-dashoffset.
Thanks!
/* Draw Line Animations */
#keyframes drawLine {
0% {
stroke-dasharray: 170;
stroke-dashoffset: 170;
}
100% {
stroke-dasharray: 170;
stroke-dashoffset: -50;
}
}
#keyframes unDrawLine {
0% {
stroke-dasharray: 170;
stroke-dashoffset: -50;
}
100% {
stroke-dasharray: 170;
stroke-dashoffset: 170;
}
}
.box {
background: red;
width: 500px;
height: 500px;
position: relative;
}
.box:hover .line {
animation: drawLine .6s cubic-bezier(.67,.02,.27,.95) forwards;
}
.line {
width: 4px;
height: 170px;
fill: none;
stroke: #fff;
stroke-width: 4px;
transform: rotate(195deg);
display: block;
position: absolute;
left: 50px;
bottom: 50px;
stroke-dasharray: 170;
stroke-dashoffset: 170;
animation: unDrawLine .6s cubic-bezier(.67,.02,.27,.95) forwards;
}
<div class="box">
<svg class="line" viewBox="0 0 4 170" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="0" x2="0" y2="170" />
</svg>
</div>
Solution (uses GreenSock)
I was able to find a solution that perfectly matched the effect I was going for while also working in Safari - it just required GSAP and the draw plugin.
Solution demo here...
https://codepen.io/anon/pen/MQqwdo
Solution explanation...
The hack/fix/trick is just slightly animating one of the coordinates of the points in the line - not noticeable to users.
I have a css animation that basically starts only after 10 seconds. However, sometimes, if I'm on another tab before the animation starts and I stay on that tab, the animation starts only when I return to the tab / page that has the animation.
document.getElementById('CircleTimer').getElementsByTagName('circle')[0].style.animation = ' countdown 10s linear infinite';
#CircleTimer circle {
stroke-dasharray: 200px;
stroke-dashoffset: 0px;
stroke-width: 2px;
stroke: #04e004;
fill: none;
}
#keyframes countdown {
from {
stroke: #04e004;
stroke-dashoffset: 0px;
}
to {
stroke: #dd0000;
stroke-dashoffset: 200px;
}
}
<svg id="CircleTimer">
<circle r="31" cx="35" cy="35"></circle>
<span id="CountDown">10</span>
</svg>
Update: I'm pretty sure now that the issue is with :
document.getElementById('CircleTimer').getElementsByTagName('circle')[0].style.animation = ' countdown 10s linear infinite';
It seems to not give those properties until I'm on the page.
I made a Jsfiddle to give you an example.
https://jsfiddle.net/8L8vfjsg/
Ok Guys here is what I have. I am looping through each dash in the polyline which I rounded to 984, its really like 984 and some change. But anyways I am seeing no movement, what I want is the lines to move around the polyline in unison. Can anyone see why this polyline is moving as the dashoffset continuously is being changed. Then i re-initate the function. Does anyone see why this guy isnt moving at all? please and thanks. I could be really fudging this up but thought Ide see if I can get another input.
loop();
function loop(){
var counter = 0;
var polyline = new Array();
for(var i = 0; i< 984; i++){
polyline[0] = getElementById('poly');
polyline[0].style.strokeDashoffset = i;
}
loop();
}
<svg id="square" width="900" height="400">
<polyline id="poly" style="stroke-linejoin: round; fill:none; stroke:black; stroke-width:8;stroke-dasharray:492.44289 492.44289;
stroke-dashoffset:0;"
3 points="0,200
450,0
900,200
450,400
0,198.4"
/>
</svg>
That infinite loop you have there seems pretty useless. You'd want to use requestAnimationFrame instead of calling the loop directly, and the loop doesn't seem to do anything useful (just sets the dashoffset to the last i value). But you don't even have to use scripting to animate the stroke.
<svg viewBox="-8 -8 916 416">
<polyline points="0,200
450,0
900,200
450,400
0,198.4" />
</svg>
and css:
polyline {
stroke-linejoin: round;
fill:red;
stroke:black;
stroke-width:8;
stroke-dasharray:492.44289 492.44289;
stroke-dashoffset:0;
animation-duration: 1s;
animation-name: march;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
}
#keyframes march {
0% {
stroke-dashoffset: 0;
}
50% {
stroke-dashoffset: 492.44289;
}
100% {
stroke-dashoffset: 984.88578;
}
}
See fiddle. And if you want to get rid of the ugly left hand corner you might try using a polygon instead of a polyline, like this.