Flash Without Flash: Draggable Panels

Next up in the Flash without Flash series is a reader submitted link, Filippa Smedhagen Sund. We’ll step through the process of recreating the draggable panels. (I know draggable isn’t a word, but it is now!) Here is my version. Note: There are some issues with IE7.

HTML

<div id="panel_container">
  <div class="panel">
    <img src="panel1.jpg" />
  </div>
  <div class="panel">
    <img src="panel2.jpg" />
  </div>
  <div class="panel">
    <img src="panel3.jpg" />
  </div>
  <div class="panel">
    <img src="panel4.jpg" />
  </div>
</div>

We have a div that contains the panels, and we have our panels. Your panels can contain whatever you want, but here, they contain images.

CSS

html {
  overflow:hidden;
}
.panel {
  cursor:move;
  float:left;
  background-color:#cc6;
  overflow:hidden;
}
.panel img {
  width:100%;
  height:100%;
}

To hide the scroll bars, we’ll set the html overflow to hidden. This means that anything not currently visible won’t ever be visible unless we use JavaScript to scroll the content. All of the panels are floated so they fall into rows and columns. In this example, the images are set to take up the full screen so they may be distorted.

JavaScript

We’ll take the JavaScript in chunks now:

var Panel = {
  start: function () {
    var height = window.getSize().size.y.toInt();
    var width = window.getSize().size.x.toInt();
    var rows = 2;
    var columns = 2;
    var current = {
      x: 0,
      y: 0
    }
    $$('.panel').setStyles({
      'height': height,
      'width': width
    });
    var container = $('container');
    container.setStyles({
      'width': width * columns,
      'height': height * rows
    });
    var scroll = container.effects({'wait': false, 'transition': Fx.Transitions.Expo.easeOut, 'duration': 400});
  }
}

Here, we set variables we’ll be using later, and set the height and width of the panels and container. An effect is also created that we’ll be using to adjust the top and left properties of the container.

var Panel = {
  start: function () {
    ...
    container.makeDraggable({
      'onBeforeStart': function () {
        this.fired = false;
      },
      'onDrag': function () {
        // move up
        if ((this.mouse.start.y - this.mouse.now.y) < -150) {
          if (current.y == 0) {
            this.stop();
            return;
          } 
          this.fired = true;
          this.stop();
          scroll.start({'top': current.y + height, 'left': current.x});
          current.y += height;
          return;
        }
        // move down
        if ((this.mouse.start.y - this.mouse.now.y) > 150) {
          if (current.y == -height * (rows - 1)) {
            this.stop();
            return;
          }
          this.fired = true;
          this.stop();
          scroll.start({'top':current.y - height, 'left': current.x});
          current.y -= height;
          return;
        }
        // move left
        if ((this.mouse.start.x - this.mouse.now.x) < -200) {
          if (current.x == 0) {
            this.stop();
            return;
          }
          this.fired = true;
          this.stop();
          scroll.start({'top': current.y, 'left': current.x + width});
          current.x += width;
          return;
        }
        // move right
        if ((this.mouse.start.x - this.mouse.now.x) > 200) {
          if (current.x == -width * (columns - 1)) {
            this.stop();
            return;
          }
          this.fired = true;
          this.stop();
          scroll.start({'top': current.y, 'left': current.x - width});
          current.x -= width;
          return;
        }
      },
      'onComplete': function () {
        if (!this.fired) {
          scroll.start({'top': current.y, 'left': current.x});
        }
      }
    });
  }
}

Now this is where the magic happens. While still inside the start function, we call the method makeDraggable() on the container. For more info on the drag functions that Mootools provides, look at Drag-Base and Drag-Move. Looking at the docs, you’ll find that it accepts an object with various options. Here, I’ll be passing in the events onBeforeStart, onDrag, and onComplete.

onBeforeStart

This event simply sets a variable that we’ll be using to test whether the mouse moved far enough to trigger switching panels.

onDrag

This might look a little complex, but you’ll notice that there are four chunks of code that look similar. Lets take a look at the first one:

// show up
if ((this.mouse.start.y - this.mouse.now.y) < -150) {
  if (current.y == 0) {
    this.stop();
    return;
  } 
  this.fired = true;
  this.stop();
  scroll.start({'top': current.y + height, 'left': current.x});
  current.y += height;
  return;
}

When we refer to this, we mean the drag object. If you take a look at the Mootools code, you will notice that it sets this.mouse.start and this.mouse.now. We’ll use these to determine how far the mouse has moved. In the snippet above, we’re testing whether the mouse has moved 150 pixels down. If it did, we do another test to see if the container should be moved down (revealing the panel above the current one). If we’re already at the top panel, we stop dragging and return so no further action is taken.

If we’re not at the top, then we’ll set fired to true, stop dragging, start the scroll effect to show the panel above, update the current y coordinate, and return just so it doesn’t bother checking the other tests.

That code block is repeated three other times, with values changed accordingly to move the container in other directions.

onComplete

Here, if the mouse wasn’t moved far enough to trigger switching panels, the current panel is moved back into view.

Finally, to get everything started, add this window.addEvent('load', Panel.start);. That’s it. Here is the demo again.

Modifications

You may notice that the functionality doesn’t exactly match the original. For example, I think locking the axis once dragging is started makes it look cleaner. To do that, you’d probably want to add something in the onSnap event that checks which axis the mouse moved the most in, and setting whichever modifier to false. See the last example at the Drag Mootoorial about limiting the axis.

Resizing the images so they retain their proportions is also something that probably needs to be done before using this. Also adding an event on window resize would be necessary to change the width and height of everything.

As for the problems in IE7, I found they could be solved by intentionally triggering an error in place of every instance where we return. I’m not sure of a more proper solution. I think I’ll have to visit the forums.

That’s it for now. If you’ve got a cool Flash link, please share it. (It saves me the time of looking for one.)

3 Responses to “Flash Without Flash: Draggable Panels”

  1. Ryan

    That’s really cool that you were able to recreate that. I like how your version doesn’t send you to new pages. One of these times, I’m going to actually mimic your things. I read along the code and it makes sense, but it won’t do me any good if I don’t do it with you.

    Keep up the fantastic job.

  2. Chris

    Yeah, I didn’t know how well I’d be able to pull it off, but it seems to have worked out pretty nicely.

    Thanks for the kind words.

  1. chromaSYNTHETIC Journal » Blog Archive » Six Flash Effects for JavaScript
    Pingback on September 25th, 2007 at 9:29 am

Leave a Reply