Viziionary Viziionary - 5 months ago 17
Javascript Question

Why is this custom volume animation slider not working as expected?

I created a custom volume animation slider but it isn't working properly. I'm actually having trouble figuring out what's happening - the volume and slider are changing, but not as expected. Here's the code:



$(function() {
var $volumeBar = $('#Volume-Bar');
var $volumeContainer = $('#Volume-Container');
var $value = $('.value');

$volumeContainer.on('mousedown', function(event) {
var height = $volumeContainer.height();
var startingCoord = event.offsetY;
var currentCoord;
var percent;
var difference;
seekingVol = true;
$volumeContainer.on('mouseup mouseleave', function() {
if (seekingVol) {
seekingVol = false;
}
});
$volumeContainer.on('mousemove', function(event) {
if (seekingVol) {
currentCoord = event.offsetY;
percent = (currentCoord / height) * 100;
$value.html(percent + '%');
$volumeBar.css({
'height': percent + '%'
});
}
});
});
});

@import url(https://fonts.googleapis.com/css?family=Bitter:700);

.value {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position:absolute;
color:white;
width:100%;
text-align:center;
font-family:Bitter;
font-size:20px;
pointer-events:none;
}
#Volume-Container {
position:relative;
width:75px;
height:150px;
background-color:#0e2030;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient:vertical;
-webkit-box-direction:normal;
-webkit-flex-direction:column;
-ms-flex-direction:column;
flex-direction:column;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
#Volume-Bar {
position:absolute;
bottom:0px;
height:30%;
width:100%;
background-color:#6ab2f2;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="Volume-Container">
<div class="value">0%</div>
<div id="Volume-Bar"></div>
</div>





What's wrong with this?

Answer

Multiple errors.

  • You're using the FlexBox model which is very new, poorly supported and often buggy. Just to put one DIV inside another anchored at the bottom, this is such a level of overkill it isn't even funny. You can centre your volume text by putting it in a DIV that matches the outermost container height (150px) and setting line-height to 150px too. Your CSS could and should be much, much simpler.
  • You're using OffsetY which Mozilla's documentation via Google describes as 'experimental'. In Safari, this seems to not work properly at all so on that browser you'll never get a working slider. You need to use a different API.
  • You've apparently not realised that coordinates start with 0 at the top of the page or containers and increasing downwards, so when you're calculating the percentage, it gets lower as the mouse pointer is closer to the top of the box instead of higher. This is a very common arrangement for coordinate handling in GUIs - (0,0) is top left, increasing positive to the right and downwards.
  • You're not rounding the calculation to an integer for display.
  • You're using jQuery animations but not cancelling any prior animation so they just stack up one after the other and jQuery will just run them in sequence until they all finish.
  • Your HTML puts the volume percentage text behind the blue slider so the slider hides the text.

Addressing some of these:

$(function() {
  var $volumeBar = $('#Volume-Bar');
  var $volumeContainer = $('#Volume-Container');
  var $value = $('.value');

  $volumeContainer.on('mousedown', function(event) {
    var height = $volumeContainer.height();
    var top = $volumeContainer.position().top;
    var startingCoord = event.clientY - top;
    var currentCoord;
    var percent;
    var difference;
    seekingVol = true;
    $volumeContainer.on('mouseup mouseleave', function() {
      if (seekingVol) {
        seekingVol = false;
      }
    });
    $volumeContainer.on('mousemove', function(event) {
      if (seekingVol) {
        currentCoord = event.clientY - top;
        percent = ((height - currentCoord) / height) * 100;
        $value.html(Math.round(percent) + '%');
        $volumeBar.finish();
        $volumeBar.animate({
          'height': percent + '%'
        });
      }
    });
  });
});

This:

  1. Reads the top of the volume container.
  2. Works out the starting coordinate based on the not-experimental clientY property, which is a page-wide coordinate with the top of the page (scrolled or otherwise) at zero, subtracting the top of the volume container from the result to get an offset within that volume container. I don't account for margin or padding here. You could do that in the script, but it'd be much easier to adjust your HTML - e.g. adding in an outer wrapper DIV - so that the #Volume-Container DIV has no margins or padding.
  3. Uses Math.round for display.
  4. Calculates the percentage as ((height - currentCoord) / height) * 100 so now, at the top of the bar, you get 100% instead of 0%.
  5. Calls jQuery .finish to cancel prior animations. In practice given your code structure and event usage, this really means that there's basically no animation on the volume slider at all. Personally, I'd ditch that animation completely and just set the height; it's much more responsive that way and looks much nicer than some weird floating laggy catchup game that you'd get otherwise (and will actually be quite hard to code for given the way the jQuery animation interface appears to work, from a quick read of their API docs).

Your percentage text fixes by just swapping the HTML ordering:

<div id="Volume-Container">
  <div id="Volume-Bar"></div>
  <div class="value">0%</div>
</div>

...but you've no doubt already found out that now, dragging the mouse over the volume slider sometimes selects the text. Fixing this is an exercise for the reader; personally, I'd probably give up and put the text underneath the volume slider where it's not going to have to be legible over different background colours as the slider moves.