Create a semicircle progress bar with rounded edges and a shadow using JavaScript and CSS

Despite my extensive search efforts, I have been unable to find a solution. My goal is to create a progress bar with rounded corners that includes a shadow. So far, this is what I have managed:

$(".progress-bar").each(function(){
  
  var bar = $(this).find(".bar");
  var val = $(this).find("span");
  var per = parseInt( val.text(), 10);

  $({p:0}).animate({p:per}, {
    duration: 3000,
    easing: "swing",
    step: function(p) {
      bar.css({
        transform: "rotate("+ (45+(p*1.8)) +"deg)"
      });
      val.text(p|0);
    }
  });
});
body{
  background-color:#3F63D3;  
}

.progress-bar{
  position: relative;
  margin: 4px;
  float:left;
  text-align: center;
}
.barOverflow{ 
  position: relative;
  overflow: hidden; 
  width: 150px; height: 70px; 
  margin-bottom: -14px;
}
.bar{
  position: absolute;
  top: 0; left: 0;
  width: 150px; height: 150px; 
  border-radius: 50%;
  box-sizing: border-box;
  border: 15px solid gray;       
  border-bottom-color: white; 
  border-right-color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="progress-bar">
  <div class="barOverflow">
    <div class="bar"></div>
  </div>
  <span>100</span>% 
</div>

I am aiming to achieve rounded corners and incorporate a shadow effect as shown in the accompanying image below. Unfortunately, I lack the expertise in creating the shadow element:

https://i.sstatic.net/ay1op.png

Although I have experimented with Progressbar.js, my understanding of SVG is limited. Any guidance or solution on this matter would be greatly appreciated.

Answer №1

@jaromanda for suggesting learning SVG.

It seems quite challenging to achieve with border-radius. Therefore, I delved into SVG and found it to be quite useful. Here is the code snippet that I came up with:

// Using the ProgressBar.js library
// Documentation: http://progressbarjs.readthedocs.org/en/1.0.0/

var bar = new ProgressBar.SemiCircle(container, {
  strokeWidth: 10,
  color: 'red',
  trailColor: '#eee',
  trailWidth: 10,
  easing: 'easeInOut',
  duration: 1400,
  svgStyle: null,
  text: {
    value: '',
    alignToBottom: false
  },
  
  // Set default step function for all animate calls
  step: (state, bar) => {
    bar.path.setAttribute('stroke', state.color);
    var value = Math.round(bar.value() * 100);
    if (value === 0) {
      bar.setText('');
    } else {
      bar.setText(value+"%");
    }

    bar.text.style.color = state.color;
  }
});
bar.text.style.fontFamily = '"Raleway", Helvetica, sans-serif';
bar.text.style.fontSize = '2rem';

bar.animate(0.45);  // Number ranges from 0.0 to 1.0
#container {
  width: 200px;
  height: 100px;
}

svg {
  height: 120px;
  width: 200px;
  fill: none;
  stroke: red;
  stroke-width: 10;
  stroke-linecap: round;
  -webkit-filter: drop-shadow( -3px -2px 5px gray );
  filter: drop-shadow( -3px -2px 5px gray );
  }
<script src="https://rawgit.com/kimmobrunfeldt/progressbar.js/1.0.0/dist/progressbar.js"></script>
<link href="https://fonts.googleapis.com/css?family=Raleway:400,300,600,800,900" rel="stylesheet" type="text/css">
<div id="container"></div>

Answer №2

One unconventional yet efficient suggestion for your current use of position: absolute is to incorporate a background color change in the circles during the animation initiation.

Incorporating this into your code:

<div class="progress-bar">
    <div class="left"></div>
    <div class="right"><div class="back"></div></div>
    <div class="barOverflow">
        <div class="bar"></div>
    </div>
    <span>0</span>%
</div>

Remember to add these CSS styles:

/* Your existing CSS code goes here */
body{
    background-color:#3F63D3;  
}

.progress-bar{
    position: relative;
    margin: 4px;
    float: left;
    text-align: center;
}
.barOverflow{ 
    position: relative;
    overflow: hidden; 
    width: 150px; height: 70px; 
    margin-bottom: -14px;
}
/* Add additional classes and styles as needed */

For jQuery functionality:

$(".progress-bar").each(function(){
    // Include your animations and logic here
    // Ensure to handle different scenarios based on progress percentage

});

View the updated code on JSFiddle

Answer №3

After experimenting with various methods, I discovered that using SVG instead of pure CSS made the implementation much simpler.

Despite my efforts, I couldn't find a straightforward solution using just HTML and CSS alone without any additional libraries, external scripts, or dependencies. The complexity of the math involved in calculating SVG transformations to represent the percentage meant that JavaScript was necessary (if there is a way to achieve this solely with HTML and CSS, I am eager to learn). However, the JavaScript script used is brief and uncomplicated, making it unnecessary to introduce another dependency to the codebase.

  • The calculations in the JavaScript are relatively simple once you go over them. It involves determining the coordinates for the end point of the gauge within the SVG's coordinate system, which essentially boils down to basic trigonometry.

  • Much of the CSS included is purely for aesthetic purposes and to enhance the visual appeal. Similar to styling any HTML shape, you can incorporate shadows or gradients as desired.

  • You can explore the codePen example here.

This code can be modified to create various circular gauge shapes (full circle, lower semi-circle, ellipsis, etc.) by tweaking it accordingly.

Hopefully, this information proves to be beneficial.

// # Credits to mxle for the initial rounded corner CSS-only solution: https://stackoverflow.com/a/42478006/4709712
// # Special thanks to Aniket Naik for the design inspiration and the foundational idea and implementation: https://codepen.io/naikus/pen/BzZoLL 
//     - Aniket Naik has an associated library mentioned in the codepen that might be worth exploring if custom implementation is not preferred

// The radius measurements in the meter-value remain consistent, ensuring x=y and never going below the minimum circle diameter connecting the two points (to maintain curve precision matching background path)
// To adjust gauge size and style, modify its parent element and utilize transform scale rather than directly editing SVG width and height properties
function percentageInRadians(percentage) {
  return percentage * (Math.PI / 100);
}

function setGaugeValue(gaugeElement, percentage, color) {
  const gaugeRadius = 65;
  const startingY = 70;
  const startingX = 10;

  const zeroBasedY = gaugeRadius * Math.sin(percentageInRadians(percentage));
  const y = -zeroBasedY + startingY;
  const zeroBasedX = gaugeRadius * Math.cos(percentageInRadians(percentage));
  const x = -zeroBasedX + gaugeRadius + startingX;

  gaugeElement.innerHTML = `<path d="M ${startingX} ${startingY}
           A ${gaugeRadius} ${gaugeRadius} 0 0 1 ${x} ${y}
           " stroke="${color}" stroke-width="10" stroke-linecap="round" />`;
}

percentageChangedEvent = (gauge, newPercentage, color) => {
  const percentage =
    newPercentage > 100 ? 100 : newPercentage < 0 ? 0 : newPercentage;
  setGaugeValue(gauge, percentage, color);
};

function initialGaugeSetup(gaugeElementId, inputId, meterColor, initialValue) {
  const gaugeElement = document.getElementById(gaugeElementId);
  setGaugeValue(gaugeElement, 0, meterColor);

  const inputElement = document.getElementById(inputId);

  inputElement.value = initialValue;
  setGaugeValue(gaugeElement, initialValue, meterColor);

  inputElement.addEventListener("change", (event) =>
    percentageChangedEvent(gaugeElement, event.target.value, meterColor)
  );
}

// Gauge Initial Configuration
initialGaugeSetup(
  "svg-graph-meter-value",
  "svg-gauge-percentage-2",
  "rgb(227 127 215)",
  40
);
body {
  background-color: rgba(0, 0, 0, 0.8);
  color: #999;
  font-family: Helvetica, sans-serif;
}


/* SVG Path Implementation */

.svg-container {
  margin: 20px auto 10px;
  height: 80px;
  width: 150px;
}

svg {
  fill: transparent;
}

.input-percent-container {
  text-align: center;
}

.input-percent-container>* {
  display: inline;
}

input {
  text-align: right;
  width: 40px;
  margin: auto;
  background-color: #5d5d5d;
  color: white;
  border-radius: 6px;
  border: black;
}
<div class="svg-container">
  <svg width="150" height="80" xmlns="http://www.w3.org/2000/svg">
      <path d="M 10 70
           A 65 65 0 1 1 140 70
           " stroke="grey" stroke-width="3" stroke-linecap="round" />
      <g id="svg-graph-meter-value">
      </g>
    </svg>
</div>
<div class="input-percent-container"><input id="svg-gauge-percentage-2" /><span>%<span/></div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to confirm that a SQL pool has been successfully created in an Express JS?

Currently, I am in the process of developing a restful API using mysql and expressjs. Below is an example showcasing how I send requests to my database: server.js: const express = require('express'), bodyParser = require('body-parser&ap ...

How can you efficiently load images as you scroll without encountering duplicates?

After researching various forums and tutorials, I have learned how to dynamically load data using AJAX calls to a database via PHP files for asynchronous loading. To display images in a random order with a limit of 12, I can use a select statement like SE ...

Video player on website experiencing issues with playing VAST ads

Hey there! Check out this awesome site for Music Videos: (Music Videos(Player)) I've been testing different options, but if you have a better suggestion, please let me know. Any help would be really appreciated. If I can't figure it out on my o ...

Creating synchronicity in your code within the useEffect hook

Is there a way to ensure that my function is fully completed before moving on, even though it's not recommended to add async to useEffect? Take a look at this code snippet: useEffect( () => { const RetrieverDataProcess = async () => ...

Mastering the art of blending images seamlessly for a captivating cross-fading effect

I'm attempting to create an image cross fade effect using the following steps: Place the selected image in the front (position: -9999 to 0) Position the previous image in the back (position: 0 to -9999) Gradually increase the opacity of the selected ...

Exploring the world of data manipulation in AngularJS

Seeking to comprehend the rationale behind it, I will share some general code snippets: 1) Fetching data from a JSON file using the "loadData" service: return { myData: function(){ return $http.get(path + "data.json"); } } 2) ...

Remove a Row from a Table by Clicking a Button with Ajax

I currently have an HTML table with 4 columns: SKU Group, Group_ID, Edit button, and Delete button. My focus at the moment is on implementing the delete functionality. I want it so that when the delete button is clicked, a confirmation box appears. If "OK" ...

The width of each column in this dataset is variable and unknown

I need to organize a varying number of items into columns. The sizes of both the items and container are unknown, as well as how many columns will fit or what width they should be. I want the browser to arrange the items in as many columns as possible with ...

Difficulty Establishing a Connection with SQL Server Using TypeORM

My local machine is running an SQL Server instance, but I'm encountering an error when trying to connect a database from TypeORM. The error message reads: originalError: ConnectionError: Failed to connect to localhost:1433 - Could not connect (seque ...

What is the alternative method for reading an HTML text file in JavaScript without utilizing the input type file?

Within the assets folder, there is a text file containing HTML that needs to be displayed within a specific component's div. Is it possible to retrieve the contents of this file and assign them to a string variable during the ngOnInit lifecycle hook ...

Retrieving the value of onCheck from the Checkbox/ListItem

Imagine I have a React.Component that displays components from material-ui: {data.map(value => ( <ListItem key={data.indexOf(value)} primaryText={value} leftCheckbox={ <Checkbox onCheck={this.pr ...

JQuery Mobile header style vanishing when applied to a page generated dynamically

My second page retrieves data from an Ajax call on the home page, but the header lacks JQuery styling and I suspect a connection between the two. Here is the HTML for the dynamically generated page: <div data-role="page" id="breakdownDialog" data-add-b ...

Problem encountered when implementing multiple filters in Vanilla JavaScript

I am facing an issue with my HTML page that contains multiple filters using Vanilla JavaScript (no jQuery). The filtering process involves counting matches of filter selections against the data attributes of each element. However, I'm puzzled as to w ...

Interact with an external JavaScript function using a button within a web application

I have a JavaScript file called dmreboot_service.js in my /js folder. By running this file with the command node /js/dmreboot_service.js, it successfully triggers a direct method in Azure. My goal is to be able to run this function or file when a button o ...

Encountering exception: Issue occurred post Node version upgrade, indicating "write after end" error

Initially, my Node.js application was operating smoothly under Node v10.29. Unfortunately, when I upgraded to "node" version . When the Node version was updated to 12.0, an error stating Caught exception: Error: write after end started occurring: Caught ...

axios interceptor - delay the request until the cookie API call is completed, and proceed only after that

Struggling to make axios wait for an additional call in the interceptor to finish. Using NuxtJS as a frontend SPA with Laravel 8 API. After trying various approaches for about 4 days, none seem to be effective. TARGET Require axios REQUEST interceptor t ...

Transforming the output byte array into a Blob results in file corruption

I am currently developing an Add-in for Word using Angular and the Office Javascript API. My goal is to retrieve a Word document through the API, convert it to a file, and then upload it to a server via POST method. The code I have implemented closely re ...

In need of assistance with posting data on a Captive Portal using Ruckus AccessPoint Javascript

We are currently working on implementing a captive portal using Ruckus AP for our guests. Our setup includes a form where users can input their username and password. Upon clicking "Login", we aim to post this data to the Ruckus AP's user_login_auth. ...

A guide on verifying if two arrays of integers are permutations using JavaScript

Is there a way to determine if two sets of integers in JavaScript are permutations? For example, given the arrays: a = [1, 2, 3, 4, 5] and b = [2, 3, 5, 1, 4] I want a function that will return true if they are permutations of each other. ...

Ajax-powered Datatables

I am a beginner to data tables and I am attempting to retrieve data from a JSON text file (test1.txt). Below is an excerpt of the data present in the file, which contains over 5000 entries: [{"0":"22352442","ID":"22352442","1":"22126303","PARENT":"2212630 ...