CSS 3 Volume Control. Part 1

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

image

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>

image

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.

image

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

image

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

image

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

image

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!

  • Related posts
  • No related posts found
  • Pingback: CSS3 Volume Control. Part 2 - Pavel Podlipensky

  • Alex

    hello, i tried to implement it but there’s no action at the progress bar, can you give me an example of the exact html that you’ve used ? Thanks, Have a good day, Alex.