Search code examples
jquerysoundcloudprogress

How to dynamically move A element into a horizontal line with jquery


I've created a custom Soundcloud Player with the widget api and a graphic progress bar made in this way:

<a id="slider-handle" href="#" style="left: 0"></a>

I dynamically move the left attribute following the current position of the track (I did a conversion from milliseconds to a percentage value). And this works fine. Now I would let the people to seek through the song moving this anchor element back and forth (like a range input). Is it possible to do this with jquery?

Here is the full code, which is more clear if you try it playing a song in the right-bottom corner: http://jsfiddle.net/7s2joet2/27/


Solution

  • I'll have a go at this:

    First, you need to set draggable="false" on the #slider-handle because browsers that support HTML5 drag & drop will show the 'forbidden' cursor when you drag it (enter image description here).

    We need the following properties to accurately position the drag handle:

    • Event.clientX: the current x coordinate of the cursor
    • Element.clientWidth: the rendered width of the element in pixels
    • Element.offsetLeft: the rendered offset of the element in pixels from the left of the page

    We need flags to keep track of state (has the user clicked? has the user left the widget?):

    • isToggled: true when a user clicks the progress bar or handle
    • isMdown: true when a user clicks the handle

    Adding the following code to yours, and changing some of the existing code:

    var isMdown     = false,    // gets set to true when user clicks the handle
        isToggled = false,  // gets set to true when user clicks the bar or handle
        progress    = $('#progress'),
        percentage= null,   // keeps track of the last % 
        handleW     = 8,            // width of the handle, req. for positioning
        handle      = document.getElementById('slider-handle'),
        range       = document.getElementById('slider-range');
    
    // main position adjustment function
    var setPos = function(e) {
      var posxBuffer = e.clientX - progress.offset().left,
          w = this.clientWidth;
      widget.getDuration(function(duration){
        widget.seekTo(percentage*duration/100);
        handle.style.left = percentage + '%';
        range.style.width = percentage + '%';
      });
      isToggled = false;
      isMdown   = false;
    };
    
    // we just need to update the % value here and set some flags
    progress.on('mousedown', function(e) {
      isToggled = true;
      var posxBuffer = e.clientX - progress.offset().left,
          w = this.clientWidth;
      percentage = ((posxBuffer-handleW)*100)/w;
      if (e.target === handle)
        isMdown = true;
    });
    
    progress.on('mouseover', function(e) {
      // if the user has previously triggered a mousedown event, he is now dragging
      // => adjust handle position, but not time progress
      if (isMdown) {
        var posxBuffer = e.clientX- progress.offset().left,
            w = this.clientWidth;
        percentage = ((posxBuffer-handleW)*100)/w;
        handle.style.left = percentage + '%';
        range.style.width = percentage + '%';
      }
    });
    
    // when a user has clicked the progress bar and releases the button,
    // set the position
    progress.on('mouseup', setPos);
    
    // when a user is still dragging but leaves the progress bar, 
    // release and set to last position
    progress.on('mouseleave', function(e) {
      if (isMdown)
        setPos(e);
    });
    
    widget.bind(SC.Widget.Events.PLAY_PROGRESS, function(){
      widget.getPosition(function(position){
        widget.getDuration(function(duration){
           $('#seekms').text(duration);  
           // Just as a bonus, the code for transforming ms into a MM:SS format =)
           var mins = (position/1000/60 < 10 ? '0' : '') + Math.floor(position/1000/60),
               secs = (position/1000%60 < 10 ? '0' : '') + Math.floor((position/1000) %60);
           $('#bufferms').text(mins + ':' + secs);
           // only updated if the user already moused down on either handler or bar
           if (isToggled || isMdown)
              return;
           var percentage = position*100/duration;
           $("#slider-range").css("width", percentage + '%');
           $("#slider-handle").css("left", percentage + '%');
                     $('#inputseek').val(percentage);
        });
      });
    });
    

    See it in action: http://jsfiddle.net/7s2joet2/31/ (note: I made the handle + bar bigger so it'd be easier to drag)