Quick Tip: Use Greensock’s Draggable.applyBounds() To Save Your Marriage And Keep Your Dog Safe

Update 23-09-14: When I posted this originally I totally forgot to include THE vital part of this tip and that’s Draggable’s ‘bounds’ property. I can’t believe I left that out. It’s like totally crucial for this to work. Sorry! *facepalms*.

Quick Tips is something I’ll be doing more of soon – these will be little ideas, methods and solutions I discover along the way.

This one involves a recent job I did for an agency that wanted a fully interactive, HD web app with all the bells and whistles. One of the components required was a zoomable, pannable, scalable map (well, it was a huge multi-billion pixel photograph of London). The simple solution to this was to use GSAP Draggable utility – if you haven’t heard of GSAP (Greensock Animation Platform) and you want ultra-performant, super slick JavaScript animations then I strongly recommend you check it out – Draggable is fully awesome and it’s only bloomin’ totally free.

So yay, the story ended happily – except it didn’t. Because the colossal touchscreen they were using to display the project had a resistive screen (as opposed to a capacitive screen you find on tablets and smart phones). Resistive screens are great in that you can use any kind of device for a click/tap (pen, finger, vegetable) but the main drawback is that they don’t support swipe gestures.

Or pinching.

Or zooming.

Or dragging.

Just poking.

So I had to employ click-and-hold functionality to do the zooming, panning and scaling and, as you might know if you’ve done this kind of thing, there are tons of gross calculations you have to do to make sure that, when you’re zoomed in a certain amount (scaled) and you want to pan left or right, your image remains inside the mask window in which it’s sitting – otherwise you end up with the image going too far into the middle and leaving a gaping margin where it should be constrained to edge of its parent window. It’s a right ball ache.

So I got all grumpy and moody because I knew I’d have to do loads of brain-mushing calculations and my head would explode and I’d shout at the dog and ignore my wife when she was talking because I’d be too preoccupied with trying to work out how to do it. I can be a right charmer.

Quick-Tip---Draggable-zooming-London
I zoomed. I panned. I got a big black area where the image wasn’t constrained. I got cross. The dog quickly left the studio.

As you can see from the image the black area was where I panned and zoomed and the image wasn’t constrained by the white window. Pff.

So anyway the solution to ensure that your image always stays constrained is to use, erm, Draggable! WTFF am I talking about I hear you mutter.

Well, a great (and somewhat hidden) feature of Draggable is a method called applyBounds(); – you probably wouldn’t need it much but it can be called on anything that has Draggable applied to it. So even though I couldn’t drag the photo with my finger I could still apply Draggable to it like this:

mapDragger = Draggable.create(myPhoto, {
bounds:zoomWindow
});

That’s all you need to do to apply it – of course there are loads of properties you can apply but as we’re not actually able to drag it on a resistive screen we just define zoomWindow as the bounds property which is the DIV in which the photo is nested.

So now I have a draggable photo, bound to its parent DIV on a device on which dragging doesn’t work. Nice. The way I apply the zooming and panning is pretty simple though. I simply set up mouse events for down and up that start and end a timer/ticker which calls a scaling function, send the direction (‘scaledown’ or ‘scaleup’) and call applyBounds() on every tick.


//add the button events
scaleDownBtn.bind('mousedown touchstart', {dir:'scaledown'}, setZoomTimer);
scaleDownBtn.bind('mouseup touchend', killZoomTimer);


function setZoomTimer(e){
//get the direction string from the data object - in this case it's scaledown
var dir = e.data.dir;
//set a timer - although this triggers immediately I could set the delay to be later so that
//when I press the button it begins scaling after, say, 1 second
zoomTimer = TweenMax.delayedCall(0, startZoomTimer, [dir]);
}


function startZoomTimer(dir){
//reference to the GSAP ticker
ticker = TweenMax.ticker;
//set a variable for the direction and set a temporary direction variable
ticker.dir = dir;
//start the ticker
ticker.addEventListener('tick', onTick, this, true, 1);
}

 

function onTick(){

//pass along the direction
doZoomAction(ticker.dir);
}


function doZoomAction(dir){

//the switch allows me to send multiple directions from different buttons ('panleft', //'panright' etc) - I've only included 'scaledown' here for brevity
switch (dir){
case 'scaledown':

//set a minimum scale
minScale = 0.54;

//set the incremental scale down
destScale = zoomMap[0]._gsTransform.scaleX - 0.01;

//set the scale (on every tick remember)
TweenMax.set(zoomMap, {
scale:(destScale >= minScale) ? destScale : minScale,
transformOrigin:'50% 50%'
});
break;
}

//the crucial part - apply Draggable applyBounds method to ensure that no matter how much you've scaled or panned

//your photo will stay within its constraining window
mapDragger[0].applyBounds();
}

function killZoomTimer(e){
try{
ticker.removeEventListener('tick', onTick);
ticker.dir = null;
} catch(e){};
TweenMax.killDelayedCallsTo(startZoomTimer);
}

So that’s it – by utilising the applyBounds method on every tick when scaling I don’t have to worry about working out where the edges of the photo are because Greensock automagically does it for me. Dog and marriage remain intact. Lovely.

5 thoughts on “Quick Tip: Use Greensock’s Draggable.applyBounds() To Save Your Marriage And Keep Your Dog Safe

  1. Fantastic. So glad to hear Draggable solving problems in somewhat unconventional ways automagically. I give you a bag of clever points though for getting this to work for this odd case. And I can totally relate on a more emotional level too. When I’m a bit cranky around the house my wife will often say “You haven’t used GSAP enough today, have you?” 😉

    I think though that when the client explains they chose a resistive screen, the proper response is “Perhaps you should consider hanging a giant poster?”

    1. Thanks Carl – you should post the cool example you created based on this code – it’d really help people to visualise it. 🙂

  2. Nice solution Chris. Love the zoom-timer code, simple yet functional. Around here my girlfriend knows right away when things are not going the right way and shoots:”something’s not working?”. Usually CSS, crossbrowser madness or clients insane requests.

      1. Ha!, I don’t know if we’re awful, but just like browsers, we’re difficult to deal with, specially in those situations.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s