knpwrs
knpwrs

Reputation: 16436

HTML5 / CSS3 Circle with Partial Border

Is it possible to create a circle using only HTML5 / CSS3 which has a border that only goes part way around the circle? If not, what techniques can I use to accomplish this effect? I would prefer to use pure DOM elements, but if I have to I can draw on canvas or spin up an SVG.

Upvotes: 32

Views: 74050

Answers (8)

Ana
Ana

Reputation: 37169

2024 solution

This method requires no JS, no extra element and not even any pseudos, just a handful of CSS declarations. It also works if the element has a (semi)transparent background in addition to this partial border.

It uses a two layer mask, one being a conic-gradient which creates a visible pie selection and whose size is relative to the border-box and the second being a simple full cover layer restricted to the padding-box.

First, we make our element circular (arbitrary width, an aspect-ratio of 1 and a border-radius set to 50%) and give it a border.

Then, we set a mask that's a conic-gradient(), relative to the border-box, covering a percentage --p of our element (by default starting from 12 o'clock and going clockwise).

On top of this mask layer, we set a full cover one restricted to the padding-box.

.circular-progress {
  border: solid 1.5em hotpink;
  width: 50vmin;
  aspect-ratio: 1;
  border-radius: 50%;
  background: hsla(180, 100%, 50%, .5);
  mask: 
    linear-gradient(red 0 0) padding-box, 
    conic-gradient(red var(--p, 17%), transparent 0%) border-box
}

/* just to make it obvious it works with semitrasparent background */
body {
  background: 
    url(https://images.unsplash.com/photo-1693483923875-cdd9ef4a8046?w=800) 
      50%/ cover
}
<div class='circular-progress'></div>

If we want to animate this, we need to also register --p.

@property --p {
  syntax: '<percentage>';
  initial-value: 0%;
  inherits: false
}

.circular-progress {
  border: solid 1.5em hotpink;
  width: 50vmin;
  aspect-ratio: 1;
  border-radius: 50%;
  background: hsla(180, 100%, 50%, .5);
  mask: 
    linear-gradient(red 0 0) padding-box, 
    conic-gradient(red var(--p), transparent 0%) border-box;
  animation: p 4s linear infinite
}

@keyframes p { to { --p: 100% } }

/* just to make it obvious it works with semitrasparent background */
body {
  background: 
    url(https://images.unsplash.com/photo-1693483923875-cdd9ef4a8046?w=800) 
      50%/ cover
}
<div class='circular-progress'></div>

As of mid 2024, registering custom properties this way in order to animate them is supported in Firefox stable as well. Chromium browsers have supported this for over half a decade and Safari support came in early 2023.

For better performance, we set inherits to false unless we really need the value of this variable to be inherited (here, we don't).

As of late 2023, mask is also supported cross-browser without a prefix.


Original answer from 2012 (preserved for web history reasons)

Yes, it is possible - see this:

.circle {
  position: relative;
  margin: 7em auto;
  width: 16em;
  height: 16em;
  border-radius: 50%;
  background: lightblue;
}

.arc {
  overflow: hidden;
  position: absolute;
  /* make sure top & left values are - the width of the border */
  /* the bottom right corner is the centre of the parent circle */
  top: -1em;
  right: 50%;
  bottom: 50%;
  left: -1em;
  /* the transform origin is the bottom right corner */
  transform-origin: 100% 100%;
  /* rotate by any angle */
  /* the skew angle is 90deg - the angle you want for the arc */
  transform: rotate(45deg) skewX(30deg);
}

.arc:before {
  box-sizing: border-box;
  display: block;
  border: solid 1em navy;
  width: 200%;
  height: 200%;
  border-radius: 50%;
  transform: skewX(-30deg);
  content: '';
}
<div class='circle'>
  <div class='arc'></div>
</div>

Upvotes: 55

ATP
ATP

Reputation: 3249

Conic gradient solution (works for any degree):

:root{
--degree:80deg;
--smoothing:0.5deg;
--color:red;
}

.a{
  height:200px;
  width:200px;
border-radius:50%;
background: conic-gradient(
     var(--color) var(--degree), transparent calc(var(--degree) + var(--smoothing)) 100%);

}

.b{
  height:84%;
  width:84%;
  top:8%;
  left:8%;
  position:relative;
  border-radius:50%;
  background:#D3D3D3;
}
<div class ="a">
    <div class="b">
</div>

Upvotes: 5

Amr nasser
Amr nasser

Reputation: 29

if you want double border you can use it also

div
{
  background-color: #ddd;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 30px;
  margin:  40px auto;
  color: black; 
  width: 200px;
  height: 200px; 
  border-radius: 50%; 
  position: relative;
}

div::before
{ 
  content: "";
  width: 200px;
  height: 200px;
  border-radius: 50%;
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-10px, -10px);
  z-index: -1;
  border: 10px solid;
  border-color: red red red transparent;
}
body ::after
{
  content: "";
  width: 220px;
  height: 220px;
  border-radius: 50%;
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-21px, -22px);
  z-index: -8;
  border: 12px solid;
  background: transparent;
  border-color: blue transparent blue blue;
}
<div> </div>

Upvotes: 0

will
will

Reputation: 346

This uses JavaScript as well, so it breaks from the original requirement :(
.. it does however deliver

There is a >> demo << here

@gkond Thanks, I derived this from your answer

// create a circle using HTML5 / CSS3 / JS which has a border that only goes part-way around
// the circle .. and which can be smoothly animated from 0% to 100% around the circle

// this solution allows for animation and still results in relatively clean code
// we use four quarter-circles, all hidden away behind a white square to start with..
// all four are rotated out clockwise, and each quarter will stop at it's own maximum:
// q1 at 25%, q2 at 50% .. etc. once we reach a value at or over 25%, all four quarters
// should be out from behind the white square, and we can hide it.. it needs to be
// hidden if we display a value over 75%, or else q4 will end up going in behind it again
// .. also, since the top border will usually sit between the angles of  -45 to 45, we
// rotate everything by an extra -45 to make it all line up with the top nicely

var fromHidden = -90;

// utility function to align 0 degrees with top
// takes degrees and returns degrees - 45
function topAlign(degrees) {
  return degrees - 45
};

// utility function to rotate a jQuery element
// takes element and the degree of rotation (from the top) 
function rotate(el, degrees) {
  var degrees = topAlign(degrees || 0);
  el.css(
    'transform', 'rotate(' + degrees + 'deg)',
    '-webkit-transform', 'rotate(' + degrees + 'deg)',
    '-moz-transform', 'rotate(' + degrees + 'deg)',
    '-ms-transform', 'rotate(' + degrees + 'deg)',
    '-o-transform', 'rotate(' + degrees + 'deg)'
  )
}

// function to draw semi-circle
// takes a jQuery element and a value (between 0 and 1)
// element must contain four .arc_q elements
function circle(el, normalisedValue) {
  var degrees = normalisedValue * 360; // turn normalised value into degrees
  var counter = 1; // keeps track of which quarter we're working with
  el.find('.arc_q').each(function() { // loop over quarters..
    var angle = Math.min(counter * 90, degrees); // limit angle to maximum allowed for this quarter
    rotate($(this), fromHidden + angle); // rotate from the hiding place
    counter++; // track which quarter we'll be working with in next pass over loop
  });
  if (degrees > 90) { // hide the cover-up square soon as we can
    el.find('.arc_cover').css('display', 'none');
  }
}

// uses the the circle function to 'animate' drawing of the semi-circle
// incrementally increses the value passed by 0.01 up to the value required
function animate(el, normalisedValue, current) {
  var current = current || 0;
  circle(el, current);
  if (current < normalisedValue) {
    current += 0.01;
    setTimeout(function() {
      animate(el, normalisedValue, current);
    }, 1);
  }
}

// kick things off ..
animate($('.circle'), 0.69);
.circle {
  position: relative;
  margin: 20px;
  width: 120px;
  height: 120px;
}

.arc_q {
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  border: 10px solid;
  border-color: transparent green transparent transparent;
  transform: rotate(-45deg);
  -webkit-transform: rotate(-45deg);
  -moz-transform: rotate(-45deg);
  -ms-transform: rotate(-45deg);
  -o-transform: rotate(-45deg);
}

.arc_cover {
  position: absolute;
  top: 0;
  left: 0;
  width: 60px;
  height: 60px;
  background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="circle">
  <div class="arc_q"></div>
  <div class="arc_q"></div>
  <div class="arc_q"></div>
  <div class="arc_q"></div>
  <div class="arc_cover"></div>
</div>

Upvotes: 13

seanculleng
seanculleng

Reputation: 61

This can be achieved setting a transparent border around a transparent circle and using border-top-color: ; to give a section of the circles border a color.

background-color:transparent;
border:3px solid transparent;
border-top-color: green;

This creates a circle with a border only around the top quarter;

You can also use

border-bottom-color:green;

as well as left and right to border different quarters of the circles circumference.

Here's a fiddle for a loader with 3 partial circles that spin inside each other in alternate directions that show this in action.

Here's a fiddle for a loader with 3 partial circles that spin inside each other in alternate directions that show this in action.

Upvotes: 2

gkond
gkond

Reputation: 4274

It's possible.

  • Draw two circles using border-radius one on top of another.
  • Make one or more arc of both circles transparent by changing border-color.
  • Use transform to rotate the second circle and you will have the arc of the size you need.

Here is the demo: http://jsfiddle.net/kJXwZ/2/

.wrapper {
  position: relative;
  margin: 20px;
}

.arc {
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  border-radius: 100%;
  border: 1px solid;
}

.arc_start {
  border-color: transparent red red red;
  -webkit-transform: rotate(45deg);
  -moz-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  -o-transform: rotate(45deg);
  transform: rotate(45deg);
}

.arc_end {
  border-color: red red red transparent;
  -webkit-transform: rotate(75deg);
  -moz-transform: rotate(75deg);
  -ms-transform: rotate(75deg);
  -o-transform: rotate(75deg);
  transform: rotate(75deg);
}
<div class="wrapper">
  <div class="arc arc_start"></div>
  <div class="arc arc_end"></div>
</div>

Upvotes: 15

Nicola Mihaita
Nicola Mihaita

Reputation: 553

Simplest way to animate this is using css keyframes.

http://jsfiddle.net/8SUPX/644/

/**
 * HTML5 / CSS3 Circle with Partial Border
 * http://stackoverflow.com/q/13059190/1397351
 */
* { margin: 0; padding: 0; }
.circle {
	position: relative;
	margin: 6em auto;
	width: 12em; height: 12em;
	border-radius: 50%;
	background: transparent;
	border:20px solid #efefef;
	border-top-color: #efefef;
    border-right-color: #efefef;
    border-bottom-color: #efefef;
    border-left-color: #efefef;
    
    -webkit-transition:.5s;-moz-transition:.5s;transition:.5s;
}
.circle:hover{
      -webkit-animation:  animix 0.5s 1;
      -webkit-animation-fill-mode: forwards;
      -moz-animation:  animix 0.5s 1;
      -moz-animation-fill-mode: forwards;
      animation:  animix 0.5s 1;
      animation-fill-mode: forwards;
}



  //Animate
   @-webkit-keyframes animix {
    0% { 
      border-top-color: transparent;
      border-right-color: transparent;
      border-bottom-color: transparent;
      border-left-color: transparent;
    }
     25% { 
       border-top-color: red;
       border-right-color: transparent;
       border-bottom-color: transparent;
       border-left-color: transparent;
     }
     50% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: transparent;
       border-left-color: transparent;
     }
     75% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: red;
       border-left-color: transparent;
     }
     100% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: red;
       border-left-color: red;
     }
   }
   
      @keyframes animix {
    0% { 
      border-top-color: transparent;
      border-right-color: transparent;
      border-bottom-color: transparent;
      border-left-color: transparent;
    }
     25% { 
       border-top-color: red;
       border-right-color: transparent;
       border-bottom-color: transparent;
       border-left-color: transparent;
     }
     50% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: transparent;
       border-left-color: transparent;
     }
     75% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: red;
       border-left-color: transparent;
     }
     100% { 
       border-top-color: red;
       border-right-color: red;
       border-bottom-color: red;
       border-left-color: red;
     }
   }
<div class="circle"> </div>

Upvotes: 0

mark stewart
mark stewart

Reputation: 421

To do this, you can use simple box border properties, one element and one class. This would be an inline, inline-block or block treatment, depending on where you place your easy-circle class, and how/if you style position.

<!DOCTYPE html>
<html>
<head>
<style>
.easy-circle {
    background: transparent;
    border: 1em solid black; /* color not required, may show device fail */
    border-color: red green blue transparent;
    height: 10em;
    width: 10em;
    -moz-border-radius: 6em; /* height/2 + border thickness */
    -webkit-border-radius: 6em;
    border-radius: 50%; /* more than 50, shape-size adjustment irrelevant */
    -ms-transform: rotate(45deg); /* IE 9 */
    -webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */
     transform: rotate(45deg); /* transform optional */
}
</style>
</head>
<body>
<div class="easy-circle">
</div>​
</body>
</html>

Upvotes: 3

Related Questions