How do I create a teardrop in HTML? - html
How do I create a shape like this to display on a webpage?
I don't want to use images since they would get blurry on scaling
I tried with CSS:
.tear {
display: inline-block;
transform: rotate(-30deg);
border: 5px solid green;
width: 50px;
height: 100px;
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
}
<div class="tear">
</div>
That turned out really screwed.
And then I tried with SVG:
<svg viewBox="0 100 100">
<polygon points="50,0 100,70 50,100 0,70"/>
</svg>
It did get the shape, but the bottom part wasn't curved.
Is there a way to create this shape so it can be used in an HTML page?
SVG approach:
You can achieve the double curve easily with an inline SVG and the <path/> element instead of the <polygon/> element which doesn't allow curved shapes.
The following example uses the <path/> element with:
2 quadratic bezier curve commands for the 2 top curves (lines beginning with Q)
1 arc command for the big bottom one (line beginning with A)
<svg width="30%" viewbox="0 0 30 42">
<path fill="transparent" stroke="#000" stroke-width="1.5"
d="M15 3
Q16.5 6.8 25 18
A12.8 12.8 0 1 1 5 18
Q13.5 6.8 15 3z" />
</svg>
SVG is a great tool to make this kind of shapes with double curves. You can check this post about double curves with an SVG/CSS comparison. Some of the advantages of using SVG in this case are:
Curve control
Fill control (opacity, color)
Stroke control (width, opacity, color)
Amount of code
Time to build and maintain the shape
Scalable
No HTTP request (if used inline like in the example)
Browser support for inline SVG goes back to Internet Explorer 9. See canIuse for more information.
Basic Border-Radius
You can do this within CSS relatively easily using border-radius' and transforms. Your CSS was just a little bit out.
.tear {
width: 50px;
height: 50px;
border-radius: 0 50% 50% 50%;
border: 3px solid black;
transform: rotate(45deg);
margin-top: 20px;
}
<div class="tear"></div>
Advanced Border-Radius
This will be very similar to above but gives it a bit more shape.
.tear {
width: 50px;
height: 50px;
border-radius: 80% 0 55% 50% / 55% 0 80% 50%;
border: 3px solid black;
transform: rotate(-45deg);
margin-top: 20px;
}
<div class="tear"></div>
Your main issue with your CSS code was:
You used a different height than width
You haven't rotated the correct angle size
So, by 'fixing' these issues, you would generate:
.tear {
display: inline-block;
transform: rotate(-45deg);
border: 5px solid green;
width: 100px;
height: 100px;
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
border-bottom-right-radius: 50%;
}
/***for demo only***/
.tear {
margin: 50px;
}
<div class="tear">
</div>
Please also note to save on CSS length, you could re-write your border-radius properties to:
border-radius: 50% 0 50% 50%;
this could be enhanced with pseudo elements as shown in this fiddle
Alternatives
I found this by Vinay Challuru on codepen.
Please note that with the logic here, I was able to create the SVG to nearly any possible build shape/etc. For example, a quick output was:
<svg viewBox='0 0 400 400'>
<path fill="none" stroke="#333" stroke-width="5" d="M200,40 C200,115 280,180 280,240 A80,80,0 0,1,120,240 C120,180 200,115 200,40" stroke-linejoin='miter'></path>
</svg>
It's using an SVG and allows you to alter the shape in multiple ways, having the ability to alter its shape to the desired result:
var SVG = function() {
this.element = document.getElementsByTagName("svg")[0];
this.namespace = "http://www.w3.org/2000/svg";
this.width = 400;
this.height = 400;
}
/****Let's initialise our SVG ready to draw our shape****/
var svg = new SVG();
/****This sets up the user interface - we've included the script for this as an external library for the codepen****/
var gui = new dat.GUI();
/****Here's where the code to create the shape begins!****/
var Teardrop = function() {
this.x = svg.width * 0.5;
this.y = svg.height * 0.1;
this.width = svg.width * 0.4;
this.triangleHeight = svg.height * 0.5;
this.yCP1 = svg.height * 0.2;
this.yCP2 = svg.height * 0.45;
this.element = null;
this.ctrlPoints = [];
this.anchors = [];
this.fill = "none";
this.stroke = "#333";
this.strokeWidth = 2;
this.showCtrlPoints = true;
this.init();
}
Teardrop.prototype.init = function() {
this.element = document.createElementNS(svg.namespace, "path");
svg.element.appendChild(this.element);
this.element.setAttribute("fill", this.fill);
this.element.setAttribute("stroke", this.stroke);
this.element.setAttribute("stroke-width", this.strokeWidth);
for (var i = 0; i < 3; i++) {
this.ctrlPoints.push(document.createElementNS(svg.namespace, "circle"));
svg.element.appendChild(this.ctrlPoints[i]);
this.ctrlPoints[i].setAttribute("fill", this.fill);
this.ctrlPoints[i].setAttribute("stroke", 'red');
this.ctrlPoints[i].setAttribute("stroke-width", 1);
this.anchors.push(document.createElementNS(svg.namespace, "line"));
svg.element.appendChild(this.anchors[i]);
this.anchors[i].setAttribute("stroke-width", 1);
this.anchors[i].setAttribute("stroke", this.stroke);
this.anchors[i].setAttribute("stroke-dasharray", "3,2");
}
this.draw();
}
Teardrop.prototype.draw = function() {
this.radius = this.width / 2;
path = [
"M", this.x, ",", this.y,
"C", this.x, ",", this.yCP1, " ", this.x + this.width / 2, ",", this.yCP2, " ", this.x + this.width / 2, ",", this.y + this.triangleHeight,
"A", this.radius, ",", this.radius, ",", "0 0,1,", this.x - this.width / 2, ",", this.y + this.triangleHeight,
"C", this.x - this.width / 2, ",", this.yCP2, " ", this.x, ",", this.yCP1, " ", this.x, ",", this.y
];
this.element.setAttribute("d", path.join(""));
cpCoords = [];
cpCoords[0] = [this.x, this.yCP1];
cpCoords[1] = [this.x - this.width / 2, this.yCP2];
cpCoords[2] = [this.x + this.width / 2, this.yCP2];
anchorCoords = [];
anchorCoords[0] = [this.x, this.y];
anchorCoords[1] = [this.x - this.width / 2, this.y + this.triangleHeight];
anchorCoords[2] = [this.x + this.width / 2, this.y + this.triangleHeight];
for (var i = 0; i < 3; i++) {
this.ctrlPoints[i].setAttribute("cx", cpCoords[i][0]);
this.ctrlPoints[i].setAttribute("cy", cpCoords[i][1]);
this.anchors[i].setAttribute("x1", cpCoords[i][0]);
this.anchors[i].setAttribute("x2", anchorCoords[i][0]);
this.anchors[i].setAttribute("y1", cpCoords[i][1]);
this.anchors[i].setAttribute("y2", anchorCoords[i][1]);
if (this.showCtrlPoints) {
this.ctrlPoints[i].setAttribute("r", 2);
this.anchors[i].setAttribute("stroke-width", 1);
} else {
this.ctrlPoints[i].setAttribute("r", 0);
this.anchors[i].setAttribute("stroke-width", 0);
}
}
}
var teardrop = new Teardrop();
gui.add(teardrop, 'triangleHeight', 0, svg.height * 0.75);
gui.add(teardrop, 'width', 0, 200);
gui.add(teardrop, 'yCP1', 0, svg.height);
gui.add(teardrop, 'yCP2', 0, svg.height);
gui.add(teardrop, 'showCtrlPoints', 0, svg.height);
for (var i in gui.__controllers) {
gui.__controllers[i].onChange(function() {
teardrop.draw();
});
}
html,
body {
height: 100%;
}
svg {
display: block;
margin: 0 auto;
background: url('http://unitedshapes.com/images/graph-paper/graph-paper.png');
}
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<svg width='400px' height='400px'></svg>
Disclaimer I did not write the above pen, only sourced it.
CSS Version
Although this is far from complete, you may also be able to generate this shape using CSS.
.tear{
height:200px;
width:200px;
background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 29%,rgba(0,0,0,1) 30%,rgba(0,0,0,1) 100%);
border-radius:50%;
margin:120px;
position:relative;
}
.tear:before{
content:"";
position:absolute;
top:-70%;left:0%;
height:100%;width:50%;
background: radial-gradient(ellipse at -50% -50%, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 75%,rgba(0,0,0,1) 76%,rgba(0,0,0,1) 100%);
}
.tear:after{
content:"";
position:absolute;
top:-70%;left:50%;
height:100%;width:50%;
background: radial-gradient(ellipse at 150% -50%, rgba(0,0,0,0) 0%,rgba(0,0,0,0) 75%,rgba(0,0,0,1) 76%,rgba(0,0,0,1) 100%);
}
<div class="tear"></div>
SVG Version
I should know that SVG should be at the top of this answer, however, I like a challenge and so here is an attempt with SVG.
svg {
height: 300px;
}
svg path {
fill: tomato;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 100 100">
<path d="M49.015,0.803
c-0.133-1.071-1.896-1.071-2.029,0
C42.57,36.344,20,43.666,20,68.367
C20,83.627,32.816,96,48,96
s28-12.373,28-27.633
C76,43.666,53.43,36.344,49.015,0.803z
M44.751,40.09
c-0.297,1.095-0.615,2.223-0.942,3.386
c-2.007,7.123-4.281,15.195-4.281,24.537
c0,5.055-2.988,6.854-5.784,6.854
c-3.189,0-5.782-2.616-5.782-5.831
c0-11.034,5.315-18.243,10.005-24.604
c1.469-1.991,2.855-3.873,3.983-5.749
c0.516-0.856,1.903-0.82,2.533,0.029
C44.781,39.116,44.879,39.619,44.751,40.09z"/>
</svg>
Altering the path values, you would be able to alter the shape of your teardrop design.
IMO this shape requires smooth curve-to beziers to ensure continuity of the curve.
The Drop in question :
For the drop in question,
smooth curves can't be used, as control points wont be of same length. But we still need to make the control points lie exactly opposite (180 deg) to the previous control points, to ensure full continuity of curve The picture given below illustrates this point :
Note: Red and blue curves are two different quadratic curves.
stroke-linejoin="miter", for the pointed top part.
AS this shape only uses successive c commands, we can omit it.
Here's the final snippet:
<svg height="300px" width="300px" viewBox="0 0 12 16">
<path fill="#FFF" stroke="black" stroke-width="0.5" stroke-linejoin="miter"
d="M 6 1 c -2 3 -5 5 -5 9
0 7 10 7 10 0
0 -4 -3 -6 -5 -9z" />
</svg>
TBH though, accepted answer's curves are not quite continuous.
For IE 5-8 (VML)
Only works in IE 5-8. VML uses different commands than SVG. Eg. it uses v for relative cubic beziers.
Note: This snippet won't run in IE 5-8 too. You need to create an html file and run it directly in the browser.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<style> v\:* { behavior: url(#default#VML); }
</style >
</head>
<body>
<div style="width:240; height:320;">
<v:shape coordorigin="0 0" coordsize="12 16" fillcolor="white" strokecolor="black" strokewidth="1"
strokeweight="5" style="width:240; height:320"
path="M 6 1 v -2 3 -5 5 -5 9
0 7 10 7 10 0
0 -4 -3 -6 -5 -9 x e">
</v:shape>
</div>
</body>
</html>
Or if your viewers' font supports it, use the Unicode characters
DROPLET: 💧 (💧)
or
BLACK DROPLET: 🌢 (🌢)
Scale accordingly!
I'd personally use an SVG for this. You can create SVGs in most vector graphics software. I'd recommend:
Inkscape
Sketch
Adobe Illustrator
I have made one below that is a tracing of your shape in Illustrator.
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="223.14px" height="319.008px" viewBox="0 0 223.14 319.008" enable-background="new 0 0 223.14 319.008" xml:space="preserve">
<path fill="none" stroke="#000000" stroke-width="12" stroke-miterlimit="10" d="M111.57,13.291c0,0,57.179,86.984,72.719,108.819
c30.359,42.66,41.005,114.694,1.626,154.074c-20.464,20.463-47.533,30.293-74.344,29.488h-0.002
c-26.811,0.805-53.88-9.025-74.344-29.488C-2.154,236.804,8.492,164.77,38.851,122.11C54.391,100.275,111.57,13.291,111.57,13.291z" />
</svg>
HTML Canvas
This is an option uncovered in this thread so far. The commands used for Canvas drawings are very similar to SVG (and web-tiki deserves the credits for the base idea used in this answer).
The shape in question can be created either using canvas' own curve commands (Quadratic or Bezier) or the Path API. The answer contains examples for all three methods.
The browser support for Canvas is quite good.
Using Quadratic Curves
window.onload = function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.lineJoin = 'miter';
ctx.moveTo(120, 20);
ctx.quadraticCurveTo(117.5, 30, 148, 68);
ctx.arc(120, 88, 34.5, 5.75, 3.66, false);
ctx.quadraticCurveTo(117.5, 35, 120, 20);
ctx.closePath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.fillStyle = '#77CCEE'
ctx.stroke();
ctx.fill();
}
}
canvas {
margin: 50px;
height: 100px;
width: 200px;
transform: scale(1.5);
}
body{
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<canvas id='canvas'></canvas>
Below is an advanced version with gradient fill and shadows. I have also included a hover effect on the shape to illustrate one drawback of Canvas when compared to SVG. Canvas is raster (pixel) based and hence would look blurred/pixelated when scaled beyond a certain point. The only solution to that would be to repaint the shape on every browser resize which is an overhead.
window.onload = function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var lineargradient = ctx.createRadialGradient(135, 95, 1, 135, 95, 10);
lineargradient.addColorStop(0, 'white');
lineargradient.addColorStop(1, '#77CCEE');
ctx.beginPath();
ctx.lineJoin = 'miter';
ctx.moveTo(120, 20);
ctx.quadraticCurveTo(117.5, 30, 148, 68);
ctx.arc(120, 88, 34.5, 5.75, 3.66, false);
ctx.quadraticCurveTo(117.5, 35, 120, 20);
ctx.closePath();
ctx.strokeStyle = '#333';
ctx.lineWidth = 3;
ctx.fillStyle = lineargradient;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(50, 50, 50, 0.5)";
ctx.stroke();
ctx.fill();
}
}
canvas {
margin: 50px;
height: 100px;
width: 200px;
transform: scale(1.5);
}
/* Just for demo */
body{
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
canvas{
transition: all 1s;
}
canvas:hover{
transform: scale(2);
}
<canvas id='canvas'></canvas>
Using Bezier Curves
window.onload = function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var lineargradient = ctx.createRadialGradient(135, 95, 1, 135, 95, 10);
lineargradient.addColorStop(0, 'white');
lineargradient.addColorStop(1, '#77CCEE');
ctx.beginPath();
ctx.lineJoin = 'miter';
ctx.arc(120, 88, 35, 5.74, 3.66, false);
ctx.bezierCurveTo(100, 55, 122, 27.5, 120, 20);
ctx.bezierCurveTo(122, 27.5, 121, 31.5, 150, 70);
ctx.closePath();
ctx.strokeStyle = 'rgba(109,195,250,0.2)';
ctx.lineWidth = 1;
ctx.fillStyle = lineargradient;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(50, 50, 50, 0.5)";
ctx.stroke();
ctx.fill();
}
}
canvas {
margin: 75px;
height: 300px;
width: 300px;
transform: scale(1.5);
}
body {
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<canvas id='canvas' height='300' width='300'></canvas>
Using Path API
window.onload = function() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.lineJoin = 'miter';
var p = new Path2D("M120 20 Q117.5 30 146 68 A34 34 0 1 1 92 68 Q117.5 35 120 20z");
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.fillStyle = '#77CCEE'
ctx.stroke(p);
ctx.fill(p);
}
}
canvas {
margin: 50px;
height: 100px;
width: 200px;
transform: scale(1.5);
}
body {
background-image: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
}
<canvas id='canvas'></canvas>
Note: As mentioned in my answere here, the Path API is not yet supported by IE and Safari.
Further reading:
7 Reasons to Consider SVGs Instead of Canvas
HTML5 Canvas vs. SVG: Choose the Best Tool for the Job
What is the difference between SVG and HTML5 Canvas?
I also found this on Codepen made by user Ana Tudor using CSS and the box-shadow style and parametric equations. Very simple, very little code. And many browsers support the CSS3 Box-shadow style:
body {
background-color: black;
}
.tear {
position: absolute;
top: 50%;
left: 50%;
margin: -0.125em;
width: 0.25em;
height: 0.25em;
border-radius: 50%;
box-shadow: 0em -5em red, 0.00118em -4.97592em #ff1800, 0.00937em -4.90393em #ff3000, 0.03125em -4.7847em #ff4800, 0.07283em -4.6194em #ff6000, 0.13915em -4.40961em #ff7800, 0.23408em -4.15735em #ff8f00, 0.36em -3.86505em #ffa700, 0.51777em -3.53553em #ffbf00, 0.70654em -3.17197em gold, 0.92382em -2.77785em #ffef00, 1.16547em -2.35698em #f7ff00, 1.42582em -1.91342em #dfff00, 1.69789em -1.45142em #c7ff00, 1.97361em -0.97545em #afff00, 2.2441em -0.49009em #97ff00, 2.5em 0.0em #80ff00, 2.73182em 0.49009em #68ff00, 2.93032em 0.97545em #50ff00, 3.08681em 1.45142em #38ff00, 3.19358em 1.91342em #20ff00, 3.24414em 2.35698em #08ff00, 3.23352em 2.77785em #00ff10, 3.15851em 3.17197em #00ff28, 3.01777em 3.53553em #00ff40, 2.81196em 3.86505em #00ff58, 2.54377em 4.15735em #00ff70, 2.21783em 4.40961em #00ff87, 1.84059em 4.6194em #00ff9f, 1.42017em 4.7847em #00ffb7, 0.96608em 4.90393em #00ffcf, 0.48891em 4.97592em #00ffe7, 0.0em 5em cyan, -0.48891em 4.97592em #00e7ff, -0.96608em 4.90393em #00cfff, -1.42017em 4.7847em #00b7ff, -1.84059em 4.6194em #009fff, -2.21783em 4.40961em #0087ff, -2.54377em 4.15735em #0070ff, -2.81196em 3.86505em #0058ff, -3.01777em 3.53553em #0040ff, -3.15851em 3.17197em #0028ff, -3.23352em 2.77785em #0010ff, -3.24414em 2.35698em #0800ff, -3.19358em 1.91342em #2000ff, -3.08681em 1.45142em #3800ff, -2.93032em 0.97545em #5000ff, -2.73182em 0.49009em #6800ff, -2.5em 0.0em #7f00ff, -2.2441em -0.49009em #9700ff, -1.97361em -0.97545em #af00ff, -1.69789em -1.45142em #c700ff, -1.42582em -1.91342em #df00ff, -1.16547em -2.35698em #f700ff, -0.92382em -2.77785em #ff00ef, -0.70654em -3.17197em #ff00d7, -0.51777em -3.53553em #ff00bf, -0.36em -3.86505em #ff00a7, -0.23408em -4.15735em #ff008f, -0.13915em -4.40961em #ff0078, -0.07283em -4.6194em #ff0060, -0.03125em -4.7847em #ff0048, -0.00937em -4.90393em #ff0030, -0.00118em -4.97592em #ff0018;
}
<div class="tear"></div>
CSS Version
As there are a fair few answers here I thought why not add to it with another method. This is using both HTML and CSS to create the teardrop.
This will allow you to change the colour of the border and background of the teardrop and also re-size the top part of it.
Using a single div we can create a circle with border and border-radius. Then using pseudo elements (:before & :after) we create a CSS triangle more here, this will act as the tip of the teardrop. Using the :before as the border we place :after on top with a smaller size and the desired background colour.
div {
width: 100px;
height: 100px;
border-radius: 50%;
border: 4px solid;
margin: 80px auto;
position: relative;
}
div:before,
div:after {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
}
div:before {
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 104px solid black;
top: -75px;
}
div:after {
border-left: 46px solid transparent;
border-right: 46px solid transparent;
border-bottom: 96px solid #fff;
top: -66px;
left: 0;
right: 0;
margin: auto;
z-index: 1;
}
<div></div>
Here is a demo of the teardrop with a background colour
div {
width: 100px;
height: 100px;
border-radius: 50%;
border: 4px solid;
background: red;
margin: 80px;
position: relative;
}
div:before,
div:after {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
}
div:before {
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid black;
top: -70px;
}
div:after {
border-left: 46px solid transparent;
border-right: 46px solid transparent;
border-bottom: 96px solid red;
top: -66px;
left: 0;
right: 0;
margin: auto;
z-index: 1;
}
<div></div>
It is as simple as putting a background colour onto the div and changing :after bottom-border colour to the same. To change the border you will need to change div border colour and :before background colour too.
It is quite easy to do this with SVG by just using an image conversion resource such as http://image.online-convert.com/convert-to-svg, which was used to create the following:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="213.000000pt" height="300.000000pt" viewBox="0 0 213.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.12, written by Peter Selinger 2001-2015
</metadata>
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1035 2944 c-143 -250 -231 -380 -508 -752 -347 -465 -432 -616 -493
-882 -91 -394 10 -753 285 -1013 508 -479 1334 -361 1677 240 126 221 165 494
105 726 -66 254 -178 452 -609 1076 -96 140 -226 335 -288 435 -155 249 -135
229 -169 170z m85 -212 c40 -69 192 -298 543 -818 268 -396 354 -593 364 -835
12 -281 -82 -509 -296 -714 -103 -99 -236 -173 -396 -221 -82 -25 -105 -27
-260 -28 -148 -1 -181 2 -255 22 -348 96 -611 357 -691 689 -41 167 -25 392
41 587 62 185 154 334 444 716 177 235 320 444 402 592 27 49 51 88 54 88 3 0
25 -35 50 -78z"/>
</g>
</svg>
Here are four progressively simpler SVG teardrop shapes:
<svg viewbox="-20 -20 180 180">
<g stroke="black" fill="none">
<path transform="translate(0)"
d="M 0 0
C 0 10 10 17 10 27
C 10 40 -10 40 -10 27
C -10 17 0 10 0 0
Z"/>
<path transform="translate(40)"
d="M 0 0
C 0 16 15 25 5 34
Q 0 38 -5 34
C -15 25 0 16 0 0
Z"/>
<path transform="translate(80)"
d="M 0 0
C 0 10 18 36 0 36
S 0 10 0 0
Z"/>
<path transform="translate(120)"
d="M 0 0
Q 18 36 0 36
T 0 0
Z"/>
<g stroke-width="0.25" stroke="red">
<g transform="translate(0)">
<ellipse rx="1" ry="1" cx="0" cy="0" />
<ellipse rx="1" ry="1" cx="0" cy="10"/>
<ellipse rx="1" ry="1" cx="10" cy="17"/>
<ellipse rx="1" ry="1" cx="10" cy="27"/>
<ellipse rx="1" ry="1" cx="10" cy="40"/>
<ellipse rx="1" ry="1" cx="-10" cy="40"/>
<ellipse rx="1" ry="1" cx="-10" cy="27"/>
<ellipse rx="1" ry="1" cx="-10" cy="17"/>
<line x1="0" y1="0" x2="0" y2="10"/>
<line x1="10" y1="17" x2="10" y2="40"/>
<line x1="-10" y1="40" x2="-10" y2="17"/>
</g>
<g transform="translate(40)">
<ellipse rx="1" ry="1" cx="0" cy="0" />
<ellipse rx="1" ry="1" cx="0" cy="16"/>
<ellipse rx="1" ry="1" cx="15" cy="25"/>
<ellipse rx="1" ry="1" cx="5" cy="34"/>
<ellipse rx="1" ry="1" cx="0" cy="38"/>
<ellipse rx="1" ry="1" cx="-5" cy="34"/>
<ellipse rx="1" ry="1" cx="-15" cy="25"/>
<line x1="0" y1="0" x2="0" y2="16"/>
<line x1="15" y1="25" x2="0" y2="38"/>
<line x1="0" y1="38" x2="-15" y2="25"/>
</g>
<g transform="translate(80)">
<ellipse rx="1" ry="1" cx="0" cy="0" />
<ellipse rx="1" ry="1" cx="0" cy="10"/>
<ellipse rx="1" ry="1" cx="18" cy="36"/>
<ellipse rx="1" ry="1" cx="0" cy="36"/>
<ellipse rx="1" ry="1" cx="-18" cy="36" stroke="gray"/>
<line x1="0" y1="0" x2="0" y2="10"/>
<line x1="18" y1="36" x2="0" y2="36"/>
<line x1="0" y1="36" x2="-18" y2="36" stroke="gray" stroke-dasharray="0.5"/>
</g>
<g transform="translate(120)">
<ellipse rx="1" ry="1" cx="0" cy="0" />
<ellipse rx="1" ry="1" cx="18" cy="36"/>
<ellipse rx="1" ry="1" cx="0" cy="36"/>
<ellipse rx="1" ry="1" cx="-18" cy="36" stroke="gray"/>
<line x1="18" y1="36" x2="0" y2="36"/>
<line x1="0" y1="36" x2="-18" y2="36" stroke="gray" stroke-dasharray="0.5"/>
</g>
</g>
</g>
<g font-size="6" transform="translate(-1.5,-4)">
<text x="-10" y="-8"># of unique points:</text>
<text transform="translate( 0)">8</text>
<text transform="translate( 40)">7</text>
<text transform="translate( 80)">4</text>
<text transform="translate(120)">3</text>
</g>
</svg>
Related
How to make inline SVG 100% of parent container
I'm trying to make an inline SVG scale to the width of the parent element. I feel like the circle elements are causing me the issue, but I'm unsure what I should be changing to achieve it. I have set the container to 300x300px, I have set the viewBox to "0 0 300 300". I assume I have to set the r, cx, and cy to half those? to which I have set to "150" but the circle is now getting cut off. I have been going round in circles (excuse the pun) changing dimensions but no luck. Your help will be greatly appreciated. Please find a link to my codepen: https://codepen.io/MayhemBliz/pen/NWyNGxj function circle() { const progressRing = document.querySelector('.progress-ring'); const circle = document.querySelector('.progress-ring__bar'); const r = circle.getAttribute('r'); const percent = progressRing.dataset.percent; const c = Math.PI * r * 2; const pct = ((0 - percent) / 100) * c; circle.style.strokeDashoffset = pct; //const percentageText = document.querySelector('.percentage'); //percentageText.textContent = percent + "%"; } window.addEventListener('load', circle); .progress-ring { width: 300px; height: 300px; display: flex; align-items: center; justify-content: center; margin: 0 auto; } .progress-ring__svg { /*transform: rotate(-90deg);*/ } .progress-ring__bar-bg, .progress-ring__bar { stroke-dashoffset: 0; transition: stroke-dashoffset 1s linear; stroke: #FF9F1E; stroke-width: 1em; } .progress-ring__bar { stroke: #666; } .percentage { position: absolute; font-size: 2.5rem; font-weight: bold; } <div class="progress-ring" data-percent="80"> <svg class="progress-ring__svg" viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg"> <circle class="progress-ring__bar-bg" r="150" cx="150" cy="150" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> <circle class="progress-ring__bar" r="150" cx="150" cy="150" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> </svg> <span class="percentage">80%</span> </div>
To avoid overflow you could use percentage based values for stroke-width and r (radius) like so: <circle r="47.5%" cx="50%" cy="50%" /> or a pixel based approximation like <circle r="142" cx="150" cy="150" /> function circle() { const progressRing = document.querySelector('.progress-ring'); const circle = document.querySelector('.progress-ring__bar'); const r = circle.getAttribute('r'); const percent = progressRing.dataset.percent; const c = Math.PI * r * 2; const pct = ((0 - percent) / 100) * c; circle.style.strokeDashoffset = pct; //const percentageText = document.querySelector('.percentage'); //percentageText.textContent = percent + "%"; } window.addEventListener('load', circle); .progress-ring { width: 300px; /*height: 300px;*/ display: flex; align-items: center; justify-content: center; margin: 0 auto; max-width:100%; } .progress-ring__svg { //width:100%; /*transform: rotate(-90deg);*/ } .progress-ring__bar-bg, .progress-ring__bar { stroke-dashoffset: 0; transition: stroke-dashoffset 1s linear; stroke: #FF9F1E; stroke-width: 5%; } .progress-ring__bar { stroke: #666; } .percentage { position: absolute; font-size: 2.5rem; font-weight: bold; } .resize{ resize:both; overflow:auto; border: 1px solid #ccc; padding:1em; } <div class="resize"> <div class="progress-ring" data-percent="80"> <svg class="progress-ring__svg" viewBox="0 0 300 300" version="1.1" xmlns="http://www.w3.org/2000/svg"> <circle class="progress-ring__bar-bg" r="47.5%" cx="50%" cy="50%" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> <circle class="progress-ring__bar" r="142" cx="150" cy="150" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> <text class="percentage" x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" dy="2%">80%</text> </svg> </div> </div> <p>Resize me</p> However, you might also use a <text> element instead of a <span> to keep the percentage text size relative to you svg boundary.
Pie chart using circle element
I am trying to create a pie chart using circle element in svg. I am able to fill values to 60%, 30% and 10% but all the circle starting from same position. How can I make next circle start from where previous one ended? svg { transform: rotate(-90deg); } circle { stroke-width: 3; stroke-opacity: 1; fill: none; } circle.stroke-yellow { stroke: yellow; stroke-dasharray: calc(2*3.14*50*60/100),calc(2*3.14*50); } circle.stroke-red { stroke: red; stroke-dasharray: calc(2*3.14*50*30/100),calc(2*3.14*50); } circle.stroke-blue { stroke: blue; stroke-dasharray: calc(2*3.14*50*10/100),calc(2*3.14*50); } <svg xmlns="http://www.w3.org/2000/svg" height="220"> <circle class="stroke-yellow" cy="110" cx="110" r="50"></circle> <circle class="stroke-red" cy="110" cx="110" r="50"></circle> <circle class="stroke-blue" cy="110" cx="110" r="50"></circle> </svg> Also stroke-width is not working which I mentioned in CSS.
As #enxaneta mentioned: you will need to give each pie segment an offset by changing the dash-offset property. Based on your code example: svg { transform: rotate(-90deg); } circle { stroke-width: 3; stroke-opacity: 1; fill: none; } .stroke { stroke-width: 100; --circumference: 314.159 } circle.stroke-blue { stroke: blue; stroke-dasharray: calc( var(--circumference) * 10 / 100), var(--circumference); stroke-dashoffset: 0; } circle.stroke-red { stroke: red; stroke-dasharray: calc( var(--circumference) * 30 / 100), var(--circumference); stroke-dashoffset: calc( 0 - var(--circumference) * 10 / 100); } circle.stroke-yellow { stroke: yellow; stroke-dasharray: calc( var(--circumference) * 60 / 100), var(--circumference); stroke-dashoffset: calc( 0 - var(--circumference) * 40 / 100); } <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" height="220"> <circle class="stroke stroke-blue stroke-10" cy="110" cx="110" r="50" /> <circle class="stroke stroke-yellow stroke-60" cy="110" cx="110" r="50" /> <circle class="stroke stroke-red stroke-30" cy="110" cx="110" r="50" /> </svg> stroke-width needs to be '100' (radius*2); Drawbacks: Firefox seems to have problems, rendering svg elements when attributes are are calculated via css calc() (SVG pie-chart working only in Chrome, not in Firefox) difficult to be reused for multiple pi chart instances Recommendations: simplify your calculations by optimizing your svg geometry. control your values (e.g pie percentages, colors) via HTML/svg attributes (or css variables) Example showing 2 slightly different svg setups: body{ font-family: arial; font-size:10px; } .icon-wrp { position: relative; display: inline-block; width: 200px; vertical-align: top; } .icon-wrp p{ font-size:12px; } <!--simple pi --> <div class="icon-wrp"> <svg class="svgPieAsset" viewBox="0 0 63.6619772368 63.6619772368"> <symbol id="slice"> <circle transform="rotate(-90 31.8309886184 31.8309886184)" id="circle" class="percent" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" /> </symbol> <!--actual pi slices --> <use class="segment" href="#slice" stroke="green" stroke-dashoffset="0" stroke-dasharray="30 100" /> <use class="segment" href="#slice" stroke="orange" stroke-dashoffset="-30" stroke-dasharray="60 100" /> <use class="segment" href="#slice" stroke="purple" stroke-dashoffset="-90" stroke-dasharray="10 100" /> </svg> <p>1. Precice geometry based on PI. <br>Should be rendered fine on all browsers.</p> </div> <div class="icon-wrp"> <svg class="svgPieAsset" viewBox="0 0 100 100"> <symbol id="slice2"> <circle transform="rotate(-90 50 50)" id="circle" class="percent" cx="50%" cy="50%" r="25" fill="none" stroke-width="50%" pathLength="100" /> </symbol> <!--actual pi slices --> <use class="segment" href="#slice2" stroke="green" stroke-dashoffset="0" stroke-dasharray="30 100" /> <use class="segment" href="#slice2" stroke="orange" stroke-dashoffset="-30" stroke-dasharray="60 100" /> <use class="segment" href="#slice2" stroke="purple" stroke-dashoffset="-90" stroke-dasharray="10 100" /> </svg> <p>2. Using pathLength="100". <br>Might show a tiny gap on chromium based browsers.</p> </div> 1. left example: Is using a precice (PI based) circle geometry The desired circumference of the circle element should be 100 svg units. Therfore we'll need to set the ideal values like so: radius: 15.91549430919 (100/2π) stroke-width: 31.8309886184 (2r) vieBox width/height: 63.6619772368 (4r) 2. right example: Is using pathLength="100" PathLength allows us to use any circle dimensions by setting the path's length computation value to "100". Unfortunately, you might encounter rendering imprecisions on some browsers (e.g. chromium based) resulting in visible gaps between pie segments. Quite likely, this issue will be fixed in future versions of chromium. Display pie segments Eitherway, you can now easily display a percentage based pie segment/slice by setting a stroke dash length value: Example 30% dash length; offset. 0 (since it's the first segment): <circle stroke-dashoffset="0" stroke-dasharray="30 100" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" /> Adding pie segments: You'll need to decrement (as we need negative values) the dash-offset values progressively by subtracting the previous dash length (percentage): 0, -30, -90 Example: 60% dash length; offset. -30 <circle stroke-dashoffset="-30" stroke-dasharray="60 100" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="31.8309886184" /> Example optimized for reusability (using css variables) .icon-wrp { position: relative; display: inline-block; width: 200px; vertical-align: top; } .chart { width: 1em; height: 1em; font-size: var(--chartFontSize); } .segment { stroke-dasharray: var(--percent) 100; stroke-dashoffset: var(--offset); stroke: var(--strokeColor); } .chartAni .segment { animation-name: progress; animation-fill-mode: forwards; animation-delay: 0.3s; animation-duration: 0.5s; transition: 0.3s; stroke-dasharray: 0 100; } #keyframes progress { from { stroke-dasharray: 0 100; stroke-dashoffset: 0; } to { stroke-dasharray: var(--percent) 100; stroke-dashoffset: var(--offset); } } <!-- pie asset – hidden --> <svg class="svgPieAsset" style="display:none" viewBox="0 0 63.6619772368 63.6619772368"> <symbol id="slice" viewBox="0 0 63.6619772368 63.6619772368"> <circle transform="rotate(-90 31.8309886184 31.8309886184)" id="circle" class="percent" cx="31.8309886184" cy="31.8309886184" r="15.9154943092" fill="none" stroke-width="31.8309886184" /> </symbol> </svg> <!-- visible pie chart --> <div class="icon-wrp"> <svg id="pieChart01" class="chart chartAni" style="--chartFontSize:20vw"> <use class="segment" href="#slice" style="--offset:-0; --percent:33.333; --strokeColor:green" /> <use class="segment" href="#slice" style="--offset:-33.333; --percent:33.333; --strokeColor:purple" /> <use class="segment" href="#slice" style="--offset:-66.666; --percent:33.333; --strokeColor:gray" /> </svg> </div> Edit: donut chart example For a donut chart or a circular gauge – just adjust the stroke-width to your needs. .icon-wrp { position: relative; display: inline-block; width: 200px; vertical-align: top; } .chart { width: 1em; height: 1em; font-size: var(--chartFontSize); } .segment { stroke-dasharray: var(--percent) 100; stroke-dashoffset: var(--offset); stroke: var(--strokeColor); } .chartAni .segment { animation-name: progress; animation-fill-mode: forwards; animation-delay: 0.3s; animation-duration: 0.5s; transition: 0.3s; stroke-dasharray: 0 100; } #keyframes progress { from { stroke-dasharray: 0 100; stroke-dashoffset: 0; } to { stroke-dasharray: var(--percent) 100; stroke-dashoffset: var(--offset); } } <!-- pie asset – hidden --> <svg class="svgPieAsset" style="display:none;" > <symbol id="slice" viewBox="0 0 33 33"> <circle id="circle" class="percent" cx="50%" cy="50%" r="15.9154943092" fill="none" stroke-width="0.95" /> </symbol> </svg> <!-- visible pie chart --> <div class="icon-wrp"> <svg id="pieChart01" class="chart chartAni" style="--chartFontSize:20vw; transform:rotate(-90deg);"> <use class="segment" href="#slice" style="--offset:0; --percent:10; --strokeColor:blue" /> <use class="segment" href="#slice" style="--offset:-10; --percent:30; --strokeColor:red" /> <use class="segment" href="#slice" style="--offset:-40; --percent:60; --strokeColor:yellow" /> </svg> </div>
If herrstrietzel's answer doesn't solve your issue for some reason: Once upon a time I started working on a blog post demonstrating how to generate simple SVG donut/pie charts in React. It's incomplete but it includes all the information you'd need to compute paths for drawing each segment in your chart. The post itself is React-centric, but the methodology doesn't require React. The snippet below was generated using the demo in that blog post. :root { --color1: #6761a8; --color2: #009ddc; --color3: #f26430; } svg { max-width: 180px; } path:nth-child(3n + 1) { fill: var(--color1); } path:nth-child(3n + 2) { fill: var(--color2); } path:nth-child(3n + 3) { fill: var(--color3); } <svg viewBox="0 0 100 100"> <path d="M50.99977962889557 22.51817981476399 L50.99993333466665 0.009999666671113516 A50 50 0 1 1 21.909411013411578 91.36325434956197 L34.92449717574351 72.99954813894905 A27.5 27.5 0 1 0 50.99977962889557 22.51817981476399"></path> <path d="M33.293128455589205 71.84331575559345 L20.27779148719977 90.20684420744341 A50 50 0 0 1 19.110270928347777 10.683023540969941 L32.65908657059322 28.656553196968876 A27.5 27.5 0 0 0 33.293128455589205 71.84331575559345"></path> <path d="M34.25580929035654 27.45292793069627 L20.707239127704607 9.479213229769087 A50 50 0 0 1 49.000066665333264 0.009999666671113516 L49.00022037110441 22.51817981476399 A27.5 27.5 0 0 0 34.25580929035654 27.45292793069627"></path> </svg> Computing the paths Each <path> represents one segment (slice) of the chart. To draw the segment you need to compute the coordinates of the 4 corners and connect them with lines and arcs. Computing the coordinates Given an angle, a radius, and a center point, you can compute an (x, y) coordinate via this formula: function getCoordinate(angleInDegrees, radius, center = 50) { // degrees to radians; const radians = angleInDegrees * (Math.PI / 180); const x = center - Math.cos(radians) * radius const y = center - Math.sin(radians) * radius; return [x, y]; } So for a 90° segment with an outer radius of 50 and an inner radius of 20, you can get the corner coordinates via: const radiusOuter = 50; const radiusInner = 20; const angleStart = 0; const angleEnd = 90; const [x1, y1] = getCoordinate(angleStart, radiusInner); // starting angle on inner radius const [x2, y2] = getCoordinate(angleStart, radiusOuter); // starting angle on outer radius const [x3, y3] = getCoordinate(angleEnd, radiusOuter); // ending angle on outer radius const [x4, y4] = getCoordinate(angleEnd, radiusInner); // ending angle on inner radius Connect the coordinates using SVG path commands: Details about each of the path commands used below can be found at MDN. const largeArc = 0; // percent > 0.5 ? 1 : 0; const sweepOuter = 1; const sweepInner = 0; const commands = [ // move to start angle coordinate, inner radius (1) `M${x1} ${y1}`, // line to start angle coordinate, outer radius (2) `L${x2} ${y2}`, // arc to end angle coordinate, outer radius (3) `A${radiusOuter} ${radiusOuter} 0 ${largeArc} ${sweepOuter} ${x3} ${y3}`, // line to end angle coordinate, inner radius (4) `L${x4} ${y4}`, // arc back to start angle coordinate, inner radius (1) `A${radiusInner} ${radiusInner} 0 ${largeArc} ${sweepInner} ${x1} ${y1}` ]; Throw it in an SVG and add a little css and you've got your segment: svg { width: 250px; border: 1px solid grey; } path { fill: tomato; } <svg viewBox="0 0 100 100"> <path d=" M30 50 L0 50 A50 50 0 0 1 50 0 L50 30 A20 20 0 0 0 30 50 "/> </svg> Repeat for the other segments.
Circle loading animation
I'm trying to create Apple's OS X circle loading animation. What I have tried so far: .animation-wrapper { width: 200px; height: 200px; border: 1px solid black; border-radius: 50%; position: relative; overflow: hidden; filter: brightness(0.8); -webkit-filter: brightness(0.8); } .pie-piece1 { position: absolute; width: 50%; height: 50%; bottom: 0; left: 0; background: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(255, 255, 0, 1) 100%); } .pie-piece2 { position: absolute; width: 50%; height: 50%; bottom: 0; right: 0; background: linear-gradient(to right, rgba(255, 255, 0, 1) 0%, rgba(0, 255, 0, 1) 100%); } .pie-piece3 { position: absolute; width: 50%; height: 50%; top: 0; left: 0; background: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(255, 0, 255, 1) 100%); } .pie-piece4 { position: absolute; width: 50%; height: 50%; top: 0; right: 0; background: linear-gradient(to right, rgba(255, 0, 255, 1) 0%, rgba(0, 0, 255, 1) 100%); } .rotating-spinners { position: absolute; } .spike { fill: rgba(22, 22, 22, 0.5); } <figure class="animation-wrapper"> <div class="pie-piece1"></div> <div class="pie-piece2"></div> <div class="pie-piece3"></div> <div class="pie-piece4"></div> <svg class="rotating-spinners" width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="spin-part" class="spike" d="M 65,-40 C 65,-40 80,20 50,50 60,40 50,-40 50,-40Z" /> </defs> <use x="0" y="0" xlink:href="#spin-part" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(60, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(120, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(180, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(240, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(300, 50, 50)" /> </svg> </figure> The linear gradients don't seem to line up correctly since i couldn't find a way to make the gradients go in two directions. Is there a way to create this using only CSS or SVG without mixing them like I have done? Or are there other solutions I can use like canvas or some kind of image magic?
Here's my SVG-only version. The background colour wheel isn't perfect, but I think I got fairly close. <svg width="135" height="135" viewBox="0 0 200 200"> <defs> <filter id="blur" color-interpolation-filters="linear"> <feGaussianBlur in="SourceGraphic" stdDeviation="11"/> </filter> <mask id="mask"> <circle cx="0" cy="0" r="90" fill="white"/> </mask> <linearGradient id="gloss" x2="0" y2="0.4"> <stop offset="0" stop-color="white" stop-opacity="0.5"/> <stop offset="1" stop-color="white" stop-opacity="0"/> </linearGradient> </defs> <g transform="translate(100,100)" mask="url(#mask)"> <g filter="url(#blur)"> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#c44"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#c09" transform="rotate(30)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#c0c" transform="rotate(60)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#90c" transform="rotate(90)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#44c" transform="rotate(120)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#09c" transform="rotate(150)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#0cc" transform="rotate(180)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#0c9" transform="rotate(210)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#4c4" transform="rotate(240)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#9c0" transform="rotate(270)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#cc0" transform="rotate(300)"/> <polygon points="0,0, -100,-26.8, -100,26.8" fill="#c90" transform="rotate(330)"/> </g> <g transform="scale(0.9,0.9)"> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4"/> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4" transform="rotate(60)"/> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4" transform="rotate(120)"/> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4" transform="rotate(180)"/> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4" transform="rotate(240)"/> <path d="M0,0C5,-61,-32,-86,-95,-90L-100,-46C-65,-53,-24,-35,0,0Z" fill="black" fill-opacity="0.4" transform="rotate(300)"/> <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0" to="360" dur="4s" repeatCount="indefinite"/> </g> <circle r="83" fill="url(#gloss)"/> <circle r="90" fill="none" stroke="black" stroke-width="2"/> </g> </svg>
Here's my effort. The conical gradient is an embedded bitmap image extracted by calculating the maximum value of each pixel in the animated GIF posted by the OP. A semi-opaque black windmill pattern is superimposed on top of that and animated, and a blur filter gets rid of the JPEG artefacts. (Edit: Added a reflective highlight to make it look a bit more 3D) <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="121" height="121" viewBox="0 0 121 121"> <defs> <clipPath id="circ"> <circle r="60" cx="60.5" cy="60.5"/> </clipPath> <linearGradient id="shine" x1="0%" y1="0%" x2="0%" y2="100%"> <stop offset="0%" style="stop-color:#fff;stop-opacity:0.6" /> <stop offset="10%" style="stop-color:#fff;stop-opacity:0.3" /> <stop offset="20%" style="stop-color:#fff;stop-opacity:0.1" /> <stop offset="40%" style="stop-color:#fff;stop-opacity:0" /> </linearGradient> <filter id="blur"> <feGaussianBlur in="SourceGraphic" stdDeviation="2"/> </filter> </defs> <image width="121" height="121" filter="url(#blur)" xlink:href="data:image/jpeg;base64, /9j/4AAQSkZJRgABAQEASABIAAD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJm cG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbG xsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAB5AHkDASEA AhEBAxEB/8QAGQAAAwEBAQAAAAAAAAAAAAAAAAECBAMF/8QAGAEAAwEBAAAAAAAAAAAAAAAAAAEC AwT/2gAMAwEAAhADEAAAAfQAXAM9O7YlzoXdLQ5QAIMSL6gA0DS50G6UyRYUyi2NBQhoHMMNzzM2 VX0BgwKQqSHJDWvTk/McagAMC0TcBUoA9LM8x49ACBjNJlawIlgl0kh8/SIAAekwbYgJiSEUc3WA AIdRBrgAhggLOfsAGxVc5xFc6WiBgN08O0AdKbqM551lKuBUjpV1OXWNN0oqohQ84V5pUr3aac8u fQ3FOpmnMKXEzWSVLX322WVXD59aamnMEkTNZzehre+ogzLn0p5lEyJzMvN6emmXQAuSIrlunmKX E9OhdHswAAAJzQ+tcbnnqtFdSAAD/8QAIBAAAgICAwADAQAAAAAAAAAAAQIAEQMwEBIgIjFAIf/a AAgBAQABBQLlsoE+TQIJ1EoTqIUE+SxcoPt3LQLWhluJkKnxkfsR/NRFzG/U8ZWoLsaYXsRj2fah 6vcG4zv+ipX4CPwH30jCm2qLapkW92NaHDLWxFvyVrUFv2U0BdJ+oODwPrz/AP/EABwRAQACAgMB AAAAAAAAAAAAAAEAEAIgETAxIf/aAAgBAwEBPwGvN/Nzox87Hod2O+T8nO+Ud2NkPKYxvHyYNNMa Ji8OjGB9sbYwNOegp0//xAAfEQABBAMAAwEAAAAAAAAAAAABAAIRIBAwMQMSIUH/2gAIAQIBAT8B pKI0BOEaD9Gk0CGX9oEMkTRugOheylTUoFTcUPajAT9DhIsz7l4irRAyRKc0jLGfpq/mPH2n/8QA HhAAAQQBBQAAAAAAAAAAAAAAEQEgITAQAAJAUHD/2gAIAQEABj8CzFEvCVDc0JYFyPADceljUvni /wD/xAAdEAADAAIDAQEAAAAAAAAAAAAAAREQMSAhQTBh/9oACAEBAAE/Ic9R2Y3vcR6iHh+A2+Hi 6LrdR1PR8m0lWNfEVtvFLilwnTpnggTTVXBnhCpClKUpSlKKUd4+evbYkRSlKUpSlKUSq+nRvaxY ZSlKUpSlKUpSC/GTlSlKUpSlLi5VypRvnOcKn0QhBInWCEJyhMQglhBomJiFFREIQgkLDGhomZiJ JI2sQSIImGhog0Qh3DbzbVrMEuDRCYrr1waqjHa6FzYxz96EoouXp0O1oLgzt6R69vlPZrl7zv/a AAwDAQACAAMAAAAQCZ9GgAVrOID+MKOo0R0OMKN+ec1rZcb1FaGkEgox777+BDUwvvfOejgbCEVt p82ChkNz+AVXCzANstqAAEMAAP/EABoRAAMBAQEBAAAAAAAAAAAAAAABERAgITH/2gAIAQMBAT8Q Eq4eeENso1wlHa1j9Jj4SVrGXUokiEHh4iExjansPgb0pS8SwvLPQkZSixjwvurR6g+C8UeEHh8S RTAYx7W1ZDHhCS1OMVDGJc/XT//EABsRAQEBAQEBAQEAAAAAAAAAAAEAERAhMSBh/9oACAECAQE/ EOHlsItTT9kstHnGIiODw4/eERZEMU6PAs4PrpyO+S3mz1vlttvdiWwXtvNtlzINvNlllDDDt5my ywyhvmGOyy2wwwwYcBZbYYYa7u/ktsMfjCGMz7w98s/0D9/J/8QAIxABAAICAgICAgMAAAAAAAAA AQARECExQSBRMGGhsXGBkf/aAAgBAQABPxDCgWxl/q4naJ0QW2rDhCfVnIGK21QfaF0xz/bgiWeL pKCIXTuzfbRYZLyX7mygIbujDSWOVotlzdeX3KUc/EAKA8y1W3DBssxuGW2eX4AL4dpnCbP68Khw NHixgcD4sUjkpn3zhfcvwBJDDgcV4K6rkl/UVS5cuXLhDpgtwMuEduFeFSmWl1yKOZUqVKm5WKlS sCCbXiB6Sh4jLgrwrJULQ6E6IFFZVisKw/mjdRdmQgjsglQalmFiokfqrtn1zQkAvwAwiomBwGGk 1BgSymPWb/iBAhgBKlYGGEiCWv5gUUZJgsY7e36ylagQIEqJEggqXoV+8IgoPFBKYS229RtpD7jH hIYYg5SFtM/cMbbeoFFHwc04MnGHKcXl/9k=" clip-path="url(#circ)" /> <g transform="translate(60.5,60.5)"> <path d="M0 0A56 56 0 0 0 0 56 56 56 0 0 0 32.916 45.305 56 56 0 0 1 0 0 56 56 0 0 0 53.259 17.305 56 56 0 0 0 53.259-17.305 56 56 0 0 1 0 0 56 56 0 0 0 32.916-45.305 56 56 0 0 0 0-56 56 56 0 0 1 0 0 56 56 0 0 0 -32.916-45.305 56 56 0 0 0-53.259-17.305 56 56 0 0 1 0 0 56 56 0 0 0 -53.259 17.305 56 56 0 0 0-32.916 45.305 56 56 0 0 1 0 0Z" stroke="none" fill="#000" opacity="0.25" transform="rotate(0)"> <animateTransform attributeName="transform" type="rotate" from="0" to="72" begin="0s" dur="0.6s" repeatCount="indefinite" /> </path> <circle r="59" stroke="#000" stroke-width="2" fill="none" opacity="0.25" /> <circle r="55" fill="url(#shine)" stroke="none" /> </g> </svg>
Canvas Approach Since this is a loading animation where the dimensions are probably going to be fixed with almost nil user interactions, Canvas would also be a good option because it doesn't add any extra elements to the DOM. Canvas drawing commands are pretty similar to SVG and the browser support is also not worse. One disadvantage would be that Canvas does not have its own blur filter (unlike SVG). But that can be overcome by either using CSS blur filter (has very low browser support) or the libraries mentioned in this Stack Overflow thread. Background Gradient Wheel: The background gradient wheel is created using an approach similar to the one detailed in my answer here. Basically we find multiple points in the circle and draw lines each of which have different colored stroke. By modifying the hue value for every line we can paint the gradient wheel. In the below screenshot the first picture shows how the background would have looked if we had drawn just 24 lines (with a hue change of 15 between every line)and the second one is our actual gradient wheel which has 360 lines in total with the hue being incremented by 1 for each line. Fan: The fan is created using the same approach as used in your SVG snippet. Path commands are used to draw each spoke. While use tag is used in SVG to repeat a shape, loops can be used in Canvas. The main difference here between SVG and Canvas is that Canvas cannot take in transform origin as a parameter for the rotate function and so context must be translated to the center point before applying rotations. Finally the canvas must be clipped into a circle because the default shape is a square (as height and width are same). The below screenshot shows the unclipped and clipped versions of the fan. This fan is then placed on top of the background gradient wheel. 3D effect: The 3D effect on top is provided by adding a small arc with an higher amount of transparency over the background and the fan. Below is the screenshot of the full picture without any animation. Animation: Animation is added by using the window.requestAnimationFrame method which calls the function passed as an argument at regular intervals. This method would generally call the function about 60 times per second (according to MDN). By incrementing the value of the counter variable during every iteration and adding it to the angle of the fan's spokes the animation effect can be achieved. window.onload = function() { var canvas = document.querySelector("#canvas"), ctx = canvas.getContext("2d"), counter = 360; function fan() { ctx.clearRect(0, 0, 100, 100); for (var i = 0; i < 360; i++) { ctx.strokeStyle = "hsl(" + (180 - i) + ", 60%, 50%)"; ctx.beginPath(); ctx.moveTo(50, 50); x = 50 + 50 * Math.cos((i / 360) * Math.PI * 2); y = 50 + 50 * Math.sin((i / 360) * Math.PI * 2) ctx.lineTo(x, y); ctx.lineWidth = 2; ctx.stroke(); } counter++; for (var j = 0; j < 6; j++) { ctx.save(); ctx.beginPath(); ctx.arc(50, 50, 50, 0, Math.PI * 2, true); ctx.clip(); ctx.translate(50, 50); ctx.rotate(((60 * j) + counter) * Math.PI / 180); ctx.beginPath(); ctx.moveTo(0, 0); ctx.bezierCurveTo(0, 0, 30, 50, 100, 0); x = 75 * Math.cos((-20 / 360) * Math.PI * 2); y = 75 * Math.sin((-20 / 360) * Math.PI * 2) ctx.lineTo(x, y); ctx.bezierCurveTo(x, y, (x - 30), (y + 40), 0, 0); ctx.closePath(); ctx.fillStyle = "rgba(0,0,0,0.5)"; ctx.fill(); ctx.restore(); } ctx.save(); ctx.beginPath(); ctx.arc(50, 50, 50, 0, Math.PI, true); ctx.arc(50, 55, 50, Math.PI, 0, false); ctx.fillStyle = "rgba(0,0,0,0.15)"; ctx.closePath(); ctx.fill(); ctx.restore(); window.requestAnimationFrame(fan); } fan(); } <canvas width='100px' height='100px' id='canvas'></canvas> SVG Approach The same approach as described above can be used with SVG also. The only downside would be the no. of extra elements that get added to the DOM both for the background and the fan. window.onload = function() { var colorWheel = document.querySelector("#color-wheel"); for (var i = 0; i < 360; i++) { lineColor = "hsl(" + (180 - i) + ", 60%, 50%)"; x = 50 + 50 * Math.cos((i / 360) * Math.PI * 2); y = 50 + 50 * Math.sin((i / 360) * Math.PI * 2); line = document.createElementNS("http://www.w3.org/2000/svg", "line"); line.setAttribute('x1', 50); line.setAttribute('y1', 50); line.setAttribute('x2', x); line.setAttribute('y2', y); line.setAttribute('stroke', lineColor); line.setAttribute('stroke-width', 2); colorWheel.appendChild(line); } } <svg class="rotating-spinners" width="100px" height="100px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="spin-part" class="spike" d="M 50,50 c0,0 30,50 100,0 l-5,-35 c0,0 -30,50 -95,35Z" /> <clipPath id="shape"> <circle r="50" cx="50" cy="50" /> </clipPath> <clipPath id="shade"> <path d='M-5,55 a55,55 1 0,1 110,0 h-5 a50,50 1 0,0 -100,0' /> </clipPath> </defs> <g id='color-wheel' clip-path='url(#shape)'> </g> <g id='fan' fill-opacity="0.5" clip-path='url(#shape)'> <use x="0" y="0" xlink:href="#spin-part" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(60, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(120, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(180, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(240, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(300, 50, 50)" /> <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0,50,50" to="360,50,50" dur="6s" repeatCount="indefinite" /> </g> <circle r='50' cx='50' cy='50' fill-opacity='0.15' clip-path='url(#shade)' /> </svg> Mixed Approach Or, if you have no problems with the extra elements for the fan but just want to avoid the 360 line elements that would get added, you could use a mixture of Canvas (for the background) and SVG for the fans like in the below snippet. window.onload = function() { var canvas = document.querySelector("#canvas"); var ctx = canvas.getContext("2d"); for (var i = 0; i < 360; i++) { ctx.strokeStyle = "hsl(" + (180 - i) + ", 60%, 50%)"; ctx.beginPath(); ctx.moveTo(50, 50); x = 50 + 50 * Math.cos((i / 360) * Math.PI * 2); y = 50 + 50 * Math.sin((i / 360) * Math.PI * 2) ctx.lineTo(x, y); ctx.lineWidth = 2; ctx.stroke(); } ctx.save(); ctx.beginPath(); ctx.arc(50, 50, 50, 0, Math.PI, true); ctx.arc(50, 55, 50, Math.PI, 0, false); ctx.fillStyle = "rgba(0,0,0,0.15)"; ctx.closePath(); ctx.fill(); ctx.restore(); } div { position: relative; height: 100px; width: 100px; } canvas, svg { position: absolute; top: 0px; left: 0px; height: 100%; width: 100%; } <script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script> <div class='container'> <canvas width='100px' height='100px' id='canvas'></canvas> <svg class="rotating-spinners" width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="spin-part" class="spike" d="M 50,50 c0,0 30,50 100,0 l-5,-35 c0,0 -30,50 -95,35Z" /> <clipPath id="shape"> <circle r="50" cx="50" cy="50" /> </clipPath> </defs> <g id='fan' fill-opacity="0.5" clip-path="url(#shape)"> <use x="0" y="0" xlink:href="#spin-part" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(60, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(120, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(180, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(240, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(300, 50, 50)" /> <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0,50,50" to="360,50,50" dur="6s" repeatCount="indefinite" /> </g> </svg> </div>
I had to do this with a mixture of SVG and CSS gradients which i know is against the request but is what i know. I used some of your original code, mostly the SVG parts for the propeller shapes. The radial gradient is made using 12 li elements. .wheel, .umbrella, .color { content: ""; position: absolute; border-radius: 50%; width: 15em; height: 15em; margin: 0; padding: 0; } .wheel { overflow: hidden; width: 15em; height: 15em; position: relative; } .umbrella { position: relative; -webkit-transform: scale(1.35); } .color, .color:nth-child(n+7):after { clip: rect(0, 15em, 15em, 7.5em); } .color:after, .color:nth-child(n+7) { content: ""; position: absolute; border-radius: 50%; left: calc(50% - 7.5em); top: calc(50% - 7.5em); width: 15em; height: 15em; clip: rect(0, 7.5em, 15em, 0); } .color:nth-child(1):after { background-color: #9ED110; transform: rotate(30deg); z-index: 12; } .color:nth-child(2):after { background-color: #50B517; transform: rotate(60deg); z-index: 11; } .color:nth-child(3):after { background-color: #179067; transform: rotate(90deg); z-index: 10; } .color:nth-child(4):after { background-color: #476EAF; transform: rotate(120deg); z-index: 9; } .color:nth-child(5):after { background-color: #9f49ac; transform: rotate(150deg); z-index: 8; } .color:nth-child(6):after { background-color: #CC42A2; transform: rotate(180deg); z-index: 7; } .color:nth-child(7):after { background-color: #FF3BA7; transform: rotate(180deg); } .color:nth-child(8):after { background-color: #FF5800; transform: rotate(210deg); } .color:nth-child(9):after { background-color: #FF8100; transform: rotate(240deg); } .color:nth-child(10):after { background-color: #FEAC00; transform: rotate(270deg); } .color:nth-child(11):after { background-color: #FFCC00; transform: rotate(300deg); } .color:nth-child(12):after { background-color: #EDE604; transform: rotate(330deg); } <div class="wheel"> <ul class="umbrella"> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> </ul> </div> These 12 elements can then be blurred together to form the smooth gradient. I then animated the spin parts to make the effect you require. var rotation = 0; $(document).ready(function() { setInterval(function() { rotation += 1; $('.wheel svg').css({ 'transform': 'rotate(' + rotation + 'deg)' });; }, 10); }); .wheel, .umbrella, .color { content: ""; position: absolute; border-radius: 50%; width: 15em; height: 15em; margin: 0; padding: 0; } .wheel { overflow: hidden; width: 15em; height: 15em; position: relative; } .umbrella { position: relative; filter: blur(.75em); -webkit-filter: blur(.75em); -moz-filter: blur(.75em); -o-filter: blur(.75em); -ms-filter: blur(.75em); filter: url(#blur); filter: progid: DXImageTransform.Microsoft.Blur(PixelRadius='.75'); -webkit-transform: scale(1.35); } .color, .color:nth-child(n+7):after { clip: rect(0, 15em, 15em, 7.5em); } .color:after, .color:nth-child(n+7) { content: ""; position: absolute; border-radius: 50%; left: calc(50% - 7.5em); top: calc(50% - 7.5em); width: 15em; height: 15em; clip: rect(0, 7.5em, 15em, 0); } .color:nth-child(1):after { background-color: #9ED110; transform: rotate(30deg); z-index: 12; } .color:nth-child(2):after { background-color: #50B517; transform: rotate(60deg); z-index: 11; } .color:nth-child(3):after { background-color: #179067; transform: rotate(90deg); z-index: 10; } .color:nth-child(4):after { background-color: #476EAF; transform: rotate(120deg); z-index: 9; } .color:nth-child(5):after { background-color: #9f49ac; transform: rotate(150deg); z-index: 8; } .color:nth-child(6):after { background-color: #CC42A2; transform: rotate(180deg); z-index: 7; } .color:nth-child(7):after { background-color: #FF3BA7; transform: rotate(180deg); } .color:nth-child(8):after { background-color: #FF5800; transform: rotate(210deg); } .color:nth-child(9):after { background-color: #FF8100; transform: rotate(240deg); } .color:nth-child(10):after { background-color: #FEAC00; transform: rotate(270deg); } .color:nth-child(11):after { background-color: #FFCC00; transform: rotate(300deg); } .color:nth-child(12):after { background-color: #EDE604; transform: rotate(330deg); } body { padding: 5px; } .wheel svg { position: absolute; top: 0; opacity: .5; } <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <figure class="animation-wrapper wheel"> <ul class="umbrella"> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> <li class="color"></li> </ul> <svg class="rotating-spinners" width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <path id="spin-part" class="spike" d="M 65,-40 C 65,-40 80,20 50,50 60,40 50,-40 50,-40Z" /> </defs> <use x="0" y="0" xlink:href="#spin-part" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(60, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(120, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(180, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(240, 50, 50)" /> <use x="0" y="0" xlink:href="#spin-part" transform="rotate(300, 50, 50)" /> </svg> </figure> Conical CSS Gradient
Here is a pure CSS solution where I will consider the use of only one element. I will rely on the shape I created in this previous answer and above I will consider a conic-gradient(). Actually the conic-gradient() is only supported in Chrome and Safari but we will soon have better support: .box { --R:50px; /*Radius*/ --c1:grey; /*first color*/ --c2:#fff; /*second color*/ --g1:var(--c1) 98%, transparent 100%; --g2:var(--c2) 98%, transparent 100%; width:calc(2*var(--R)); height:calc(2*var(--R)); border-radius:100%; border:1px solid; position:relative; overflow:hidden; display:inline-block; background:conic-gradient(rgba(128, 0, 128,0.7), rgba(0, 0, 255,0.7),rgba(0, 128, 0,0.7),rgba(255, 255, 0,0.7), rgba(255, 0, 0,0.7), rgba(128, 0, 128,0.7)); box-shadow:0 3px 5px inset rgba(0,0,0,0.5); } .box::before, .box::after{ content:""; position:absolute; z-index:-1; top:0; bottom:0; left:0; width:50%; background: /*we rotate by 30deg so will use : sin(30deg)*R = 0.5xR cos(30deg)*R = 0.866xR */ radial-gradient(circle var(--R) at calc(var(--R) + 0.866*var(--R)) calc(var(--R) - 0.5*var(--R)) ,var(--g1)), radial-gradient(circle var(--R) at calc(var(--R) + 0.5*var(--R)) calc(var(--R) - 0.866*var(--R)),var(--g2)), radial-gradient(circle var(--R) at var(--R) 0 ,var(--g1)), radial-gradient(circle var(--R) at calc(var(--R) - 0.5*var(--R)) calc(var(--R) - 0.866*var(--R)),var(--g2)), radial-gradient(circle var(--R) at calc(var(--R) - 0.866*var(--R)) calc(var(--R) - 0.5*var(--R)) ,var(--g1)), radial-gradient(circle var(--R) at 0 var(--R) ,var(--g2)), radial-gradient(circle var(--R) at calc(var(--R) - 0.866*var(--R)) calc(var(--R) + 0.5*var(--R)) ,var(--g1)); transform-origin:right; animation:animate 3s linear infinite; } /*the same shape rotated*/ .box::after { animation-delay:-1.5s; } #keyframes animate { to { transform:rotate(-360deg); } } <div class="box"></div> Output Here is in the other direction (like your animation) .box { --R:50px; /*Radius*/ --c1:grey; /*first color*/ --c2:#fff; /*second color*/ --g1:var(--c1) 98%, transparent 100%; --g2:var(--c2) 98%, transparent 100%; width:calc(2*var(--R)); height:calc(2*var(--R)); border-radius:100%; border:1px solid; position:relative; overflow:hidden; display:inline-block; background:conic-gradient(rgba(128, 0, 128,0.7), rgba(0, 0, 255,0.7),rgba(0, 128, 0,0.7),rgba(255, 255, 0,0.7), rgba(255, 0, 0,0.7), rgba(128, 0, 128,0.7)); box-shadow:0 3px 5px inset rgba(0,0,0,0.5); } .box::before, .box::after{ content:""; position:absolute; z-index:-1; top:0; bottom:0; left:0; width:50%; background: /*we rotate by 30deg so will use : sin(30deg)*R = 0.5xR cos(30deg)*R = 0.866xR */ radial-gradient(circle var(--R) at calc(var(--R) + 0.866*var(--R)) calc(var(--R) - 0.5*var(--R)) ,var(--g1)), radial-gradient(circle var(--R) at calc(var(--R) + 0.5*var(--R)) calc(var(--R) - 0.866*var(--R)),var(--g2)), radial-gradient(circle var(--R) at var(--R) 0 ,var(--g1)), radial-gradient(circle var(--R) at calc(var(--R) - 0.5*var(--R)) calc(var(--R) - 0.866*var(--R)),var(--g2)), radial-gradient(circle var(--R) at calc(var(--R) - 0.866*var(--R)) calc(var(--R) - 0.5*var(--R)) ,var(--g1)), radial-gradient(circle var(--R) at 0 var(--R) ,var(--g2)), radial-gradient(circle var(--R) at calc(var(--R) - 0.866*var(--R)) calc(var(--R) + 0.5*var(--R)) ,var(--g1)); transform-origin:right; animation:animate 3s linear infinite; } /*the same shape rotated*/ .box::after { animation-delay:-1.5s; } #keyframes animate { from { transform:scale(-1,1) rotate(0deg); } to { transform:scale(-1,1) rotate(-360deg); } } <div class="box"></div> Output
Circular percent progress bar
I would like to have an percent circle indicator on my site: In this case it's showing 75%. How should this be done? I have the yellow circle in a image-file, but if it's easier to, some how, do it all using CSS, that's okay with me.
Considering the shape of the progress bar (rounded end/start) I would suggest using SVG. DEMO: Radial progress bar In the following example, the progress is animated with the stroke-dasarray attribute and the % numbers are incremented with jQuery: var count = $(('#count')); $({ Counter: 0 }).animate({ Counter: count.text() }, { duration: 5000, easing: 'linear', step: function () { count.text(Math.ceil(this.Counter)+ "%"); } }); body{text-align:center;font-family: 'Open Sans', sans-serif;} svg{width:25%;} <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <svg id="animated" viewbox="0 0 100 100"> <circle cx="50" cy="50" r="45" fill="#FDB900"/> <path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff" stroke-dasharray="251.2,0" d="M50 10 a 40 40 0 0 1 0 80 a 40 40 0 0 1 0 -80"> <animate attributeName="stroke-dasharray" from="0,251.2" to="251.2,0" dur="5s"/> </path> <text id="count" x="50" y="50" text-anchor="middle" dy="7" font-size="20">100%</text> </svg> Unfortunatly IE doesn't support svg SMIL animations. To achieve the same result with IE support, you can use a library like snap.svg and animate the stroke-dasharray attribute with JS : var count = $(('#count')); $({ Counter: 0 }).animate({ Counter: count.text() }, { duration: 5000, easing: 'linear', step: function () { count.text(Math.ceil(this.Counter)+ "%"); } }); var s = Snap('#animated'); var progress = s.select('#progress'); progress.attr({strokeDasharray: '0, 251.2'}); Snap.animate(0,251.2, function( value ) { progress.attr({ 'stroke-dasharray':value+',251.2'}); }, 5000); body{text-align:center;font-family:sans-serif;} svg{width:25%;} <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script> <svg id="svg" viewbox="0 0 100 100"> <circle cx="50" cy="50" r="45" fill="#FDB900"/> <path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff" stroke-dasharray="1,250.2" d="M50 10 a 40 40 0 0 1 0 80 a 40 40 0 0 1 0 -80"/> <text x="50" y="50" text-anchor="middle" dy="7" font-size="20">1%</text> </svg> <svg viewbox="0 0 100 100"> <circle cx="50" cy="50" r="45" fill="#FDB900"/> <path fill="none" stroke-linecap="round" stroke-width="5" stroke="#fff" stroke-dasharray="125.6,125.6" d="M50 10 a 40 40 0 0 1 0 80 a 40 40 0 0 1 0 -80"/> <text x="50" y="50" text-anchor="middle" dy="7" font-size="20">50%</text> </svg> <svg id="animated" viewbox="0 0 100 100"> <circle cx="50" cy="50" r="45" fill="#FDB900"/> <path id="progress" stroke-linecap="round" stroke-width="5" stroke="#fff" fill="none" d="M50 10 a 40 40 0 0 1 0 80 a 40 40 0 0 1 0 -80"> </path> <text id="count" x="50" y="50" text-anchor="middle" dy="7" font-size="20">100%</text> </svg>
Pure HTML/CSS Solution In short, I used the CSS border-radius and z-index properties to create a white half circle layered beneath an orange center circle and orange outer ring. Initially, the half-circle is completely hidden behind an additional layer of orange on the left side, but its outer edge is gradually revealed as it rotates around the center axis (via CSS transform: rotate()), creating the appearance of a progress bar. Additional trickery is required once the progress bar passes the halfway point (see below the snippet for more info). All of this was done with pure HTML and CSS, except the animation, which uses JavaScript. It may appear to require more code than the SVG-based solutions, but the markup is actually much simpler, which makes it a good alternative in my opinion. function setProgress(elem, percent) { var degrees = percent * 3.6, transform = /MSIE 9/.test(navigator.userAgent) ? 'msTransform' : 'transform'; elem.querySelector('.counter').setAttribute('data-percent', Math.round(percent)); elem.querySelector('.progressEnd').style[transform] = 'rotate(' + degrees + 'deg)'; elem.querySelector('.progress').style[transform] = 'rotate(' + degrees + 'deg)'; if(percent >= 50 && !/(^|\s)fiftyPlus(\s|$)/.test(elem.className)) elem.className += ' fiftyPlus'; } (function() { var elem = document.querySelector('.circlePercent'), percent = 0; (function animate() { setProgress(elem, (percent += .25)); if(percent < 100) setTimeout(animate, 15); })(); })(); .circlePercent { position: relative; top: 26px; left: 26px; width: 96px; height: 96px; border-radius: 50%; background: orange; } .circlePercent:before, .circlePercent > .progressEnd { position: absolute; z-index: 3; top: 2px; left: 45px; width: 6px; height: 6px; border-radius: 50%; background: white; -ms-transform-origin: 3px 46px; transform-origin: 3px 46px; content: ""; } .circlePercent:after, .circlePercent > .progress { position: absolute; -ms-transform-origin: 48px 48px; transform-origin: 48px 48px; z-index: 0; top: 0; left: 0; width: 48px; height: 96px; border-radius: 48px 0 0 48px; background: orange; content: ""; } .circlePercent.fiftyPlus:after { background: white; -ms-transform: rotate(180deg); transform: rotate(180deg); } .circlePercent > .progress.progress { background: white; } .circlePercent > .counter { position: absolute; box-sizing: border-box; z-index: 2; width: 100px; height: 100px; margin-top: -2px; margin-left: -2px; border-radius: 50%; border: 4px solid orange; } .circlePercent > .counter:before { position: absolute; z-index: 1; top: 50%; margin-top: -13px; width: 100%; height: 26px; font-size: 26px; line-height: 26px; font-family: sans-serif; text-align: center; color: white; content: attr(data-percent) "%"; } .circlePercent > .counter:after { position: absolute; width: 80px; height: 80px; top: 6px; left: 6px; border-radius: 50%; background: orange; content: ""; } .circlePercent > .counter[data-percent="100"] { background: white; } <div class="circlePercent"> <div class="counter" data-percent="0"></div> <div class="progress"></div> <div class="progressEnd"></div> </div> Notice that the fiftyPlus class is added to the container element once the progress bar reaches the halfway point. This adds a static progress bar to the right half of the circle. Without it, the progress will appear to drain out of the right side instead of filling in the left side.
Demo : CODEPEN Here is my attempt at getting the percentage circle indicator: Path draws two arches and i set the stroke-dasharray. we don't really need to set the dasharray since we set that later with javascript. Html <svg class="circles" viewbox="0 0 200 200"> <g id="first"> <circle r="50" cx="100" cy="100" fill="#fb0"/> <path fill="none" stroke-linecap="round" stroke-width="7" stroke="#ffa" stroke-dasharray="250,250" d="M100 60 A1 1 0 0 1 100 140 A1 1 0 0 1 100 60"/> <text class="circ-text" text-anchor="middle" x="100" y="100" font-size="12px" fill="#ffa" >percentage </text> </g> </svg> Javascirpt Get the path with #first path. Get the length of the dash-array: path.getTotalLength(); Increase the dash-array until its full: setAttribute('stroke-dasharray', i+","+length); Where i is increased. Find out what percentage we are at: (count++/ticks)*100 add the percentage to the svg text: text.innerHTML=(count/tick)*100 var path = document.querySelector('#first path'); var text = document.querySelector('#first .circ-text'); var length = path.getTotalLength(); var i = 0; var count = 0; var ticks = 50; setInterval(function() { if (i < length+length/ticks) { path.setAttribute('stroke-dasharray', i+","+length); i+=length/ticks; text.innerHTML=Math.round((count++/ticks)*100); } }, 100); var path = document.querySelector('#first path'); var text = document.querySelector('#first .circ-text'); var length = path.getTotalLength(); var i = 0; var count = 0; var ticks = 50; setInterval(function() { if (i < length+length/ticks) { path.setAttribute('stroke-dasharray', i+","+length); i+=length/ticks; text.innerHTML=Math.round((count++/ticks)*100); } }, 100); <svg class="circles" viewbox="0 0 500 200"> <g id="first"> <circle r="50" cx="100" cy="100" fill="#fb0" /> <path fill="none" stroke-linecap="round" stroke-width="7" stroke="#ffa" stroke-dasharray="250,250" d="M100 60 A1 1 0 0 1 100 140 A1 1 0 0 1 100 60" /> <text class="circ-text" text-anchor="middle" x="100" y="100" font-size="12px" fill="#ffa">percentage</text> </g>
Circular Percent Progress bar using Canvas ... var ctx = document.getElementById('circularLoader').getContext('2d'); var al = 0; var start = 4.72; var cw = ctx.canvas.width; var ch = ctx.canvas.height; var diff; function progressSim(){ diff = ((al / 100) * Math.PI*2*10).toFixed(2); ctx.clearRect(0, 0, cw, ch); ctx.lineWidth = 17; ctx.fillStyle = '#4285f4'; ctx.strokeStyle = "#4285f4"; ctx.textAlign = "center"; ctx.font="28px monospace"; ctx.fillText(al+'%', cw*.52, ch*.5+5, cw+12); ctx.beginPath(); ctx.arc(100, 100, 75, start, diff/10+start, false); ctx.stroke(); if(al >= 100){ clearTimeout(sim); // Add scripting here that will run when progress completes } al++; } var sim = setInterval(progressSim, 50); <div id="loader"> <canvas id="circularLoader" width="200" height="200"></canvas> </div> See Demo :- http://codingflag.in/mysketch.php?sketch=9
Have you tried this? <percent-display percent="75" side="" colors=""></percent-display> Source: http://angularscript.com/angularjs-round-percentage-bar-directive-ngpercentdisplay/
Draw a crescent moon using SVG in HTML
Is it possible to draw a crescent moon using SVG in HTML? I've been trying things out at W3 Schools but I don't see an example of this. I don't need any of the shading that you see in typical google images of 'crescent moon', just a solid border crescent moon.
Rather than drawing three circles you can draw a path with two arcs: <path d="M50 20A40 40 0 1 0 50 70 30 30 0 1 1 50 20z" stroke="black" stroke-width="2" fill="red"/> This reads as M 50 20 Move to position (50, 20) A 40 40 0 1 0 50 70 Draw an arc from that point to position (50, 70). The arc should have a radius of 40 in the x-axis and 40 in the y axis. 30 30 0 1 1 50 20 Draw another arc in the opposite direction from the current point to (50, 20) with a radius of 30 in both axes. z Join the ends nicely For more information see SVG specification
Please note that my solution might not be the best. I am not an expert when it comes to vector graphics and you should definitely look for other solution The approach I took is to draw another image with the same background. It will look like: To Eliminate the extra border I draw another circle above it Now Set the 3rd image border to white it will look like: If you are not using borders you only need to use 2 circles You might also wanna take a look at clipping and masking. It might be a better approach. <!DOCTYPE html> <html> <body> <h1>My first SVG</h1> <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" /> <circle cx="115" cy="50" r="30" stroke="black" stroke-width="2" fill="white" /> <circle cx="130" cy="50" r="23" stroke="white" stroke-width="2" fill="white" /> </svg> </body> </html> Side note : W3Schools isn't really the reference you should be relying on. It's full of wrong information and outdated stuff. Better resources include: Mozilla developers network Web Platform
I found the existing answers lacked the poetry of an Easter Egg, were not based on science and were not that user friendly. Here is an icon I built for 'dark mode': <svg id="moon" viewBox="-6 -6 12 12"> <title>Dark mode</title> <defs> <mask id="earth"> <rect fill="white" x="-5" y="-5" width="10" height="10"></rect> <circle fill="black" cx="3.141592654" r="5"/> </mask> </defs> <circle r="5" fill="currentColor" mask="url(#earth)" transform="rotate(-23.5)"/> </svg> Things to know: The viewbox is centered Width and height is not provided as this is a vector graphic that scales Scale can be specified in CSS The standard svg namespace definition is not needed in evergreen browsers the mask is an offset circle, offset by pi just for to pay homage to the circle the icon is tilted by 23.5 degrees as that is the tilt of the earth the id tags are sensibly named, 'earth' for the mask and 'moon' for the icon the fill is currentColor so that the icon is black on a white page and white on a black page for brevity the origin of the circles is not given as this defaults to the centre of the viewbox, with the viewbox having a negative origin if a fixed size is needed then width and height can be added if used inline in a document as a clickable icon then the CSS needs to have pointer-events: bounding-box specified so that there is a click area for usability you can change the <title> to be what you want the viewbox includes padding, to increase it change the viewbox accordingly the icon should weigh in at a lot less than 256 bytes If Easter Eggs are morally wrong in your workplace and you can't inline your SVGs then you can save this oneliner as an image and use it that way: <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="-6 -6 12 12"><defs><mask id="a"><rect width="10" height="10" x="-5" y="-5" fill="#fff"/><circle cx="3" r="5"/></mask></defs><circle r="5" fill="currentColor" mask="url(#a)" transform="rotate(-23)"/></svg> This looks the same at icon size even though there are no floating point numbers. If you want to use the icon inline in your CSS rather than your document (keeping presentation out of markup) then you can specify a CSS variable with the SVG content and then use that in a pseudo selector. In your script for changing to 'dark mode' you can update the CSS variable or if you want some informative text as well then you can do the usual add a class to hide it method, e.g.:: [].map.call(document.querySelectorAll('button'), function (event) { event.addEventListener("click", function (event) { [].map.call(document.querySelectorAll('button'), function (el) { el.classList.toggle('hide'); }); }); }); :root { --icon-moon-dark: url('data:image/svg+xml,\ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-6 -6 12 12"> \ <defs> \ <mask id="earth"> \ <rect fill="white" x="-5" y="-5" width="10" height="10"></rect> \ <circle fill="black" cx="3.141592654" r="5"/> \ </mask> \ </defs> \ <circle r="5" fill="white" mask="url(%23earth)" transform="rotate(-23.5)"/> \ </svg>'); --icon-moon-light: url('data:image/svg+xml,\ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-6 -6 12 12"> \ <defs> \ <mask id="earth"> \ <rect fill="white" x="-5" y="-5" width="10" height="10"></rect> \ <circle fill="black" cx="3.141592654" r="5"/> \ </mask> \ </defs> \ <circle r="5" fill="black" mask="url(%23earth)" transform="rotate(-23.5)"/> \ </svg>'); } .hide { display: none; } nav > button { padding:0; font-size: x-large; } #dark-mode span { display: grid; grid-template-columns: auto 1fr; align-items: center; grid-auto-flow: column; text-transform: uppercase; font-family: fantasy; padding: .5em 1em; } #dark-mode span::before { content: var(--icon-moon-light); width: 1.5em; height: 1.5em; } #light-mode span { background: black; color: white; display: grid; grid-template-columns: auto 1fr; align-items: center; grid-auto-flow: column; text-transform: uppercase; font-family: cursive; padding: .5em 1em; } #light-mode span::before { content: var(--icon-moon-dark); width: 1.5em; height: 1.5em; } <nav><button id="dark-mode" class="hide"><span>Light mode</span></button> <button id="light-mode"><span>Dark mode</span></button><nav>
Here's a JS/CSS solution that doesn't require SVG, in case it's useful to anyone: http://codebox.org.uk/pages/html-moon-planet-phases (disclaimer - I wrote it)
For the records: CSS only version (no need for SVG) (updated with solid border) http://jsfiddle.net/KA3yp/1/ HTML: <div class="crescent"></div> CSS: .crescent { width: 300px; height: 300px; background-color: #f00; border-radius:150px; position: relative; } .crescent:before { content: ""; width:220px; height: 220px; display: block; position: absolute; border-radius: 110px; right: -5px; top: 40px; background: #fff; border: 2px solid #000; } .crescent:after { background: none repeat scroll 0 0 #FFFFFF; border-radius: 100px; content: ""; display: block; height: 140px; position: absolute; right: -18px; top: 83px; width: 140px; }
I use Raphael.js to draw this moon here: http://jsfiddle.net/franky85/SX3g8/ Or use the code like this: var paper = Raphael(0, 0, "100%", "100%"); var backgroundColor = 'white'; var moonColor = 'darkorange'; var c = paper.circle(50, 50, 40).attr({ fill: moonColor, 'stroke-width': 0 }); var e = paper.ellipse(50, 50, 20, 40).attr({ fill: backgroundColor, 'stroke-width': 0 }); var r = paper.rect(50, 10, 40, 80).attr({ fill: backgroundColor, 'stroke-width': 0 }); var set = paper.set(); set.push(c); set.push(e); set.push(r); For more complex SVG draw, you'd better use Raphael.js lib. The document of Raphael.js is here: http://raphaeljs.com/reference.html