There is always a place for inspiration in developer’s life. Especially when it comes to web design of real world things such as volume control for your fancy CD player. So I decided to take a challenge and design similar to Nikolay Samoylov’s sound control by using CSS3, minimum of JavaScript and no images of course.
Our final result will look like this

My target browser will be WebKit only (don’t have time to do cross-browser support), and for more complexity let it be Mobile WebKit.
So there are two main tasks with that control – how to make the progress bar (yellow line) moving by circle and how to make volume knob look like steel one (w/o images).
Let’s start with simple HTML mockup. We need to draw three different circles here, so we have three separate divs in our mockup
<div class="volume">
<div id="volume-knob">
</div>
<div id="progress-bar">
</div>
</div>

The main idea behind circle-based progress bar is to have a half of circle with bigger radius behind volume knob, so we will see only rounded orange stripe around the knob.

Then we can rotate progress bar element while user drag the knob.

But we want to show only part of the progress bar, i.e. sector of the half of the circle. We can achieve this effect by clipping half of the progress bar parent (right part in the example above), so only sector will look out of the parent

But this will allow us to show only progress bar for the half of the circle. Once left half of the progress bar will be orange we need to start drawing second half of the progress bar (right one) and we should remove clip from parent as we should show left part along with right part

We finished with left part rotation (colored in red on the image above for better recognition) and we start showing/rotating right part (one more half of the circle). Right part will overlap left part, but as they have the same color it will not be noticeable.
So let’s add left and right parts of the progress bar to our markup
<div class="volume">
<div id="volume-knob">
</div>
<div id="progress-bar">
<div id="l-bar">
</div>
<div id="r-bar">
</div>
</div>
</div>
And here is our first version of css styles
#volume-knob {
width: 250px;
height: 250px;
-webkit-border-radius: 125px; /* make volume knob displayed as a circle by using huge border radius */
display:block;
background: #DDD;
position: absolute;
top: 15px;
left: 15px;
z-index: 3; /* volume knob should overlap progress bar */
}
#progress-bar {
clip: rect(0px, 140px, 280px, 0px); /* show left half of the progress bar circle only*/
display: block;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
}
#l-bar,
#r-bar { /* left and right parts of the circle have the same styles except rotation */
-webkit-border-radius: 280px; /* make progress bar displayed as a circle */
border-radius: 280px;
background: #FF9900;
position: absolute;
top: 0px;
left: 0px;
width: 280px;
height: 280px;
z-index: 1;
}
#r-bar {
display: none; /* initially we don’t show right part of progress bar at all. It will be shown once user will reach 50% of the progress bar */
-webkit-transform: rotate(0deg); /* set initial position of right half of the circle */
clip: rect(0px, 280px, 280px, 140px); /* clip right half of the circle */
}
#l-bar {
clip: rect(0px, 140px, 280px, 0px); /* clip left half of the circle */
-webkit-transform: rotate(180deg); /* set initial position of left half of the circle */
}
Now let’s add some javascript to redraw progress bar as user drags volume knob:
(function() {
//global variables will be available only inside this closure
var isStartMove = false, knob, lBar, rBar, pos, center, pin;
//calculates control's position related to top left corner of the page
function findPos(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return {X: curleft, Y: curtop};
}
//user touch the knob
function onKnobTouchStart(e){
if (!e) var e = window.event;
isStartMove = true;
}
//user is moving his finger (mouse), so do update UI accordingly
function onKnobTouchMove(e){
if (!e) var e = window.event;
if(isStartMove){
var a = Math.atan2((e.pageY - center.Y), (e.pageX - center.X)) * 180 / Math.PI; //calculate angle
var b = (a >= 90 ? a - 90 : 270 + a); //shift angle, so 0 will be on the same place as usual 270
displayProgress(b);
}
}
//user removed finger from screen (mouseup event on desktop)
function onKnobTouchEnd(e){
if (!e) var e = window.event;
isStartMove = false;
}
//rotate progress bar to a given angle
function displayProgress(a){
if(a >= 0 && a <= 180){
progressBar.style.clip ="rect(0px, 140px, 280px, 0px)";//show left part of the progress bar
lBar.style.webkitTransform = "rotate(" + -1 * (180 - a) + "deg)";
rBar.style.display = "none";
}
else if(a > 180 && a < 360){
progressBar.style.clip ="rect(0px, 280px, 280px, 0px)";//show right part of the progress bar
rBar.style.display = "block"; //show right part of the progress bar
lBar.style.webkitTransform = "rotate(0deg)"; //left part of the progress bar is static until user make volume lower than a half
rBar.style.webkitTransform = "rotate(" + a + "deg)";
}
}
window.onload = function(){
//find elements on a page
knob = document.getElementById('volume-knob');
progressBar = document.getElementById('progress-bar');
lBar = document.getElementById('l-bar');
rBar = document.getElementById('r-bar');
knob.addEventListener('touchstart', onKnobTouchStart, false);
knob.addEventListener('touchmove', onKnobTouchMove, false);
knob.addEventListener('touchend', onKnobTouchEnd, false);
pos = findPos(knob);//find position of the element on the page
center = {X: pos.X + knob.offsetWidth / 2, Y: pos.Y + knob.offsetHeight / 2}; //calculate center of the knob (and control itself)
displayProgress(0); //display progress at 0 degree from start
};
})();
There is nothing sophisticated in my javascript code – just simple math to calculate angles and events handlers to react on user’s actions.
But we want to have progress bar less than 360 deg. (as we have Min/Max values). Let’s say we want to have just 300 deg. of yellow stripe around the volume knob. Then we can rotate progress bar on 30 deg. clockwise and prevent progress bar to be more than 300 deg. while “rotating” the knob. Changes will affect javascript and css code:
#progress-bar {
clip: rect(0px, 140px, 280px, 0px);
display: block;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
-webkit-transform: rotate(30deg);
}
//user is moving his finger (mouse), so do update UI accordingly
function onKnobTouchMove(e){
if (!e) var e = window.event;
if(isStartMove){
var a = Math.atan2((e.pageY - center.Y), (e.pageX - center.X)) * 180 / Math.PI; //calculate angle
var b = (a >= 90 ? a - 90 : 270 + a) - 30 ; //shift angle, so 0 will be on the same place as usual 270
displayProgress(b);
}
}
//rotate progress bar to a given angle
function displayProgress(a){
if(a >= 0 && a <= 180){
progressBar.style.clip ="rect(0px, 140px, 280px, 0px)";//show left part of the progress bar
progressBar.style.webkitTransform ="rotate(30deg)";
lBar.style.webkitTransform = "rotate(" + -1 * (180 - a) + "deg)";
rBar.style.display = "none";
}
else if(a > 180 && a < 300){
progressBar.style.clip ="rect(0px, 280px, 280px, 0px)";//show right part of the progress bar
rBar.style.display = "block"; //show right part of the progress bar
lBar.style.webkitTransform = "rotate(0deg)"; //left part of the progress bar is static until user make volume lower than a half
rBar.style.webkitTransform = "rotate(" + a + "deg)";
}
}
I think its enough for first version, checkout the demo and wait for second part of the tutorial where we will make it looking awesome by using no images!