Circular percent progress bar - html

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/

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.

Simple svg css progress circle

I am trying to look for a way to achieve a simple progress circle (static) with no animations. The examples I have found have very different offsets for percentage such as given in the example below. How do I make my progress circle in such a way that if I provide offset as 50%, then it is exactly 50% (half filled)?
.u-absoluteCenter {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
.u-flexCenter {
display: flex;
align-items: center;
justify-content: center;
}
.u-offscreen {
position: absolute;
left: -999em;
}
.demo {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
.progress {
transform: rotate(-90deg);
}
.progress__value {
stroke-dasharray: 0;
stroke-dashoffset: 0;
}
#-webkit-keyframes progress {
from {
stroke-dashoffset: 339.292;
}
to {
stroke-dashoffset: 0;
}
}
#keyframes progress {
from {
stroke-dashoffset: 339.292;
}
to {
stroke-dashoffset: 0;
}
}
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12"
stroke-dasharray="339.292" stroke-dashoffset="339.292" />
</svg>
You can leverage an SVG attribute to set the path length rather than having to calculate it.
pathLength sets the length to whatever you need...say 100 for a progress bar.
The pathLength attribute lets authors specify a total length for the path, in user units. This value is then used to calibrate the browser's distance calculations with those of the author, by scaling all distance computations using the ratio pathLength/(computed value of path length).
pathLength="100"
Then you can set the stroke-dasharray to 100 as well and then adjust the stroke-dashoffset as needed....
::root {
--val: 0;
}
svg {
transform: rotate(-90deg);
}
.percent {
stroke-dasharray: 100;
stroke-dashoffset: calc(100 - var(--val));
}
.fifty {
--val: 50;
}
.sixty {
--val: 60;
}
.ninety {
--val: 90;
}
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent fifty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent sixty" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="54" fill="none" stroke="#e6e6e6" stroke-width="12" />
<circle class="percent ninety" cx="60" cy="60" r="54" fill="none" stroke="#f77a52" stroke-width="12" pathLength="100" />
</svg>
As Paulie says, pathLength is the key to progress circles
A modern Custom Element (supported in all modern browsers) makes for a re-usable HTML Element
<svg-progress-circle percent="30"></svg-progress-circle>
<svg-progress-circle percent="20" color="blue"></svg-progress-circle>
<svg-progress-circle percent="80" color="gold"></svg-progress-circle>
Added a range-input for interactive demo purposes.
Percent is a property on the element, you can set with code like:
document.getElementById("Slider1").percent = <PERCENTAGE>;
If you don't want a dashed grey fullcircle, delete the dash setting from the pathLenght=120 path
I used a path instead of overlapping circles because with some other settings the almost same code can create pie-charts.
<style>
svg { width: 150px; background: teal }
svg-progress-circle[percent="100"] path { stroke: green }
</style>
<svg-progress-circle percent="30"></svg-progress-circle>
<svg-progress-circle percent="20" color="blue"></svg-progress-circle>
<svg-progress-circle percent="80" color="gold"></svg-progress-circle>
<script>
customElements.define("svg-progress-circle", class extends HTMLElement {
connectedCallback() {
let d = 'M5,30a25,25,0,1,1,50,0a25,25,0,1,1,-50,0'; // circle
this.innerHTML =
`<input type="range" min="0" max="100" step="10" value="30"`+ // delete 2 lines
` oninput="this.parentNode.percent=this.value" /><br>`+ // just for demo
`<svg viewBox="0 0 60 60">
<path stroke-dasharray="10 2" stroke-dashoffset="-19"
pathlength="120" d="${d}" fill="grey" stroke="lightgrey" stroke-width="5"/>
<path stroke-dasharray="30 70" stroke-dashoffset="-25"
pathlength="100" d="${d}" fill="none"
stroke="${this.getAttribute("color")||"red"}" stroke-width="5"/>
<text x="50%" y="57%" text-anchor="middle">30%</text></svg>`;
this.style.display='inline-block';
this.percent = this.getAttribute("percent");
}
set percent(val = 0) {
this.setAttribute("percent", val);
let dash = val + " " + (100 - val);
this.querySelector("path+path").setAttribute('stroke-dasharray', dash);
this.querySelector("text").innerHTML = val + "%";
this.querySelector("input").value = val;
}
})
</script>
Note: I am working on a complete Web Component that does Pie Graphs and fancy Progress circles like:
But, it is one of many side-projects... HTML examples and obfuscated source code available at https://pie-meister.github.io/

SVG property vector-effect="non-scaling-stroke" still scales stroke

In SVG, I need the PATH stroke width to remain as is while viewBox property is changing. The SVG property vector-effect="non-scaling-stroke" should accomplish that but it is not working as it suppose to.
Can someone explain why in the code below (check the codepen.io) the stroke width still increases as viewbox changes? I would also appreciate a solution that makes strokes width constant regardless of viewbox.
https://codepen.io/anon/pen/eKQrYL
HTML
<div class="Item">
<div class="Item-graphic">
<svg id='scaling-stroke' width="200" height="200" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="none" stroke="#fff" stroke-width="2"/>
<path d="M25 15 L 25 35" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
<path d="M15 25 L 35 25" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<span>
50 x 50 view box<br>
200 x 200 dimensions<br>
no vector effect
</span>
</div>
<div class="Item">
<div class="Item-graphic">
<svg id='non-scaling-stroke' width="200" height="200" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="none" stroke="#fff" stroke-width="2" vector-effect="non-scaling-stroke"/>
<path d="M25 15 L 25 35" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"/>
<path d="M15 25 L 35 25" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"/>
</div>
</svg>
<span>
50 x 50 view box<br>
200 x 200 dimensions<br>
vector effect
</span>
</div>
CSS
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background-color: #2196F3;
font-family: Helvetica, sans-serif;
font-weight: 300;
line-height: 1.5;
}
svg {
display: block;
margin: 0 auto;
}
.Item {
flex: 0 0 200px;
padding: 0 1rem;
color: rgba(#fff, 0.6);
font-size: 11px;
text-align: center;
}
.Item-graphic {
display: flex;
align-items: center;
height: 220px;
}
JS
n=1;inc=1;
cvb = function(){
vb = '' + n
vb += ' ' + n
vb += ' ' + 2*(25-n)
vb += ' ' + 2*(25-n)
$('#non-scaling-stroke').attr('viewBox', vb)
$('#scaling-stroke').attr('viewBox', vb)
n += inc;
if (n<=1 || n>=24) inc *= -1;
setTimeout(cvb, 100);
};
cvb()
It is a bug in Chrome. The bug report is here https://bugs.chromium.org/p/chromium/issues/detail?id=849080. It was fixed in Chrome version 68.0.3440.25.

How do I create a teardrop in 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>