Chapter 12. DrawSVG and Draggable

Draggable

Dragging objects around your screen on the web is one of those things that seems like it would be pretty easy to implement—until you try to do it from scratch. There’s a lot to account for, with touch input, mouse events, viewports, scroll behavior, friction, and believable physics. There are more failure conditions than you would probably initially consider. Thankfully, GreenSock’s Draggable is a really powerful plug-in and works perfectly on SVG as well as HTML elements.

Draggable is device-enabled for touchscreens, uses requestAnimationFrame(), and is GPU-accelerated. Draggable works on its own but is more powerful when coupled with the ThrowPropsPlugin, which creates really beautiful physics-like motion.

One of the best things about Draggable is its simplicity. This is all it takes to make a box realistically draggable:

 Draggable.create(".box", {type:"x,y", edgeResistance:0.65,
  bounds:"#container", throwProps:true});

You may notice in the preceding code that we’ve defined some boundaries with bounds. bounds is pretty flexible: you can define containing units or pixel parameters. Something like "#container" (as in the preceding example) or section would work, but you could also say {top:10, left:10, width:800, height:600} in an object to restrict the movement.

You can also have it lock movement along the horizontal or vertical axis if you like by setting lockAxis:true, which will work in both directions.

There are lots of callbacks/event listeners available to you:

  • onPress
  • onDragStart
  • onDrag
  • onDragEnd
  • onRelease
  • onLockAxis
  • onClick

so you can do things like:

myBox.addEventListener("dragend", functionName);

this refers to the Draggable instance itself, so you can easily access its target or bounds. That’s extremely helpful, because if you are going to plug into any of Draggable’s offerings, there’s no guessing or console logging to figure out what you’re referring to. All of this also works on transformed elements as well, and honors transform-origin, which is also a little hairy to write with native methods—but the plug-in makes it simple and straightforward. Figure 12-1 and its corresponding example are demos of this plug-in.

Figure 12-1. A really simple Mr. Potato Head pen that allows you to drag pieces of an SVG around to mix and match its features
var features = "#top_hat, #moustache, #redhat, #curly-moustache, 
    #eyes1, #lips, #toothy-lips, #toupe, #toothy, #big-ear-r, 
    #big-ear-l, #shoes1, #lashed, #lash2, #lazy-eyes, #longbrown-moustache, 
    #purplehat, #sm-ear-r, #sm-ear-l, #earring-r, #earring-l, #highheels, 
    #greenhat, #shoes2, #blonde, #blond-mustache, #elf-r, #elf-l";



Draggable.create(features, {
    edgeResistance:0.65,
    type:"x,y",
    throwProps:true,
    autoScroll:true
});

Drag Types

So far we’ve only shown how to use Draggable with x and y values, meaning flicking and dragging things around the screen up and down, left and right. But there are other drag types to choose from, "rotation" and "scroll":

Draggable.create("#wheel", {type: "rotation"});

Using Draggable in a rotation can be fun and engaging for controls like knobs, gears, levers, and pulleys. You can also define a minRotation and a maxRotation.

I don’t tend to mess around with scrolling unless I have to, but it’s important to mention that Draggable can be used for this as well. You can control the scrollTop and/or scrollLeft properties of an element; pass a Boolean value for lockAxis; and define edgeResistance, which would be a number.

One thing I do like that relates to scrolling but applies to x,y is the ability to do something like autoScroll:1 (or autoScroll:2, etc.). What this does is allow the viewport to be scrolled if the element that you’re moving goes outside of the display area. Let’s say you have a box dragging around a screen. If you pass in autoScroll:1, the scroll will follow the edge of the box, which is the behavior you’d expect, so it looks pretty natural.

hitTest()

One of the coolest features of Draggable is its custom collision detection. This opens up tons of possibilities for drag-and-drop UIs, interaction, and even things like games.  ​

In the following code, we’re checking if the elements are overlapping by more than 80%. If they are, we add a class (which will add a red border), and if not, we remove it:

var droppables = document.querySelectorAll('.box'),
    overlapThreshold = "80%"; 

Draggable.create(droppables, {
  bounds:window,
  onDrag: function(e) {
    var i = droppables.length;
     while (--i > -1) {
       if (this.hitTest(droppables[i], overlapThreshold)) {
         droppables[i].classList.add("red-border");
       } else {
         droppables[i].classList.remove("red-border");
       }
    }
  }
});

You could also use hitTest() logic to detect if a mouseover happens, or any other kind of target.

We can write code like this from scratch with native methods, and use things like getBBox() or getBoundingClientRect() to calculate coordinate values. In fact, I’ll show you how to work with some of these things for cool effects in Chapter 15. But if you’re already loading and working with the Draggable plug-in for drag events, it makes sense to make use of the awesome utilities available to you.

Using Draggable to Control a Timeline

One of my favorite ways to work with Draggable is to pair it with other effects. Plotting a timeline with Draggable can create some beautiful scenes and give the user the power to control the progress. See Figure 12-2 and its corresponding example.

Figure 12-2. The user can choose to drag the scene in progressively by dragging the gear in rotation or by hitting play

In the interest of brevity, I’m not showing all the code to animate everything on the page here, but rather only focusing on how we’re creating the interaction:

var master = new TimelineMax({paused:true});
master.add(sceneOne(), "scene1");

// master.seek("scene1");

Draggable.create(gear, {
  type: "rotation",
  bounds: {
    minRotation: 0,
    maxRotation: 360
  },
  onDrag: function() {
    master.progress((this.rotation)/360 );
  }
});

We pause the timeline initially, and then create a draggable instance of the gear. We define the bounds using minRotation and maxRotation so that the user can’t keep dragging the gear forever. We also specify that the timeline will be plotted along the 360 degrees of the rotation using progress(). progress() is incredibly useful for this kind of timeline manipulation, because it allows you to create a range for the timeline’s progress or manipulate events along certain points of the timeline.

You can also plot the interaction along straight lines, or anything else you like—there are so many possibilities!

DrawSVG

How would we make an SVG that looks like it’s drawing itself on the page? Well, actually, we already learned this in Chapter 6 when we went over the Chartist with CSS animation example.

Loading Plug-ins

DrawSVG is a paid-for plug-in, but chances are you’ll want to play with it before you spend money. GreenSock makes this possible by providing CodePen-safe versions of its plug-ins, which you can use in pens as you like.

Don’t forget to load the plug-in resources and TweenMax.min.js into your pen before you get started.

Let’s review this technique.

The first thing we need is an SVG element. This element has a stroke, and that stroke is dashed (see Figure 12-3).

Figure 12-3. Star shape with a dashed stroke

We can use a native method in JavaScript called .getTotalLength() to get the length of the shape:

var starID = document.getElementById('star');
console.log( starID.getTotalLength() );

We’ll then set one of the dashes along the path, the stroke-dasharray, to the whole length of the shape (the integer we got from the console output). The offset, or stroke-dashoffset, is the distance into the pattern at which the start of the path is positioned.  Since we’re only setting one value for the array, it should be automatically duplicated so that gaps and dashes are equal in length. This is animatable, either with CSS without a framework or with JavaScript:

.path {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: dash 5s linear forwards;
}

@keyframes dash {
  to {
    stroke-dashoffset: 0;
  }
}

So, why would we use GreenSock to animate something that is easily animatable in CSS without a framework? Here are a few reasons:

  • You’re already loading GreenSock, and the plug-in allows you to animate these properties easily without calculating lengths.
  • It’ll work with regular rect, circle, ellipse, polyline, and polygon shapes, which don’t have a .getTotalLength() method.
  • The .getTotalLength() method is static and won’t work on scaling SVGs that are adjusting responsively, whereas DrawSVG will.
  • .getTotalLength() has some nasty bugs in IE and Firefox. If you just use it to console.log the value and then delete it, you can work around this problem, but not if you’d like to use this method to update dynamically.
  • With GreenSock, you are not only able to animate from a beginning to an ending integer; you can use Booleans (true means fully drawn, false means not drawn at all) or percentages (my favorite!), or you can even have it animate in and collapse, with values like "50% 50%" (as both points start at 50%, they will not appear at all).

DrawSVG makes it easy to do very complex and stable animations, and when it’s paired with other features like staggering, timelines, and relative HSL tweening (covered in the previous chapters), you can create beautiful effects easily, with only a single line of code:

TweenMax.staggerFromTo($draw, 4,{ drawSVG:'0' }, { drawSVG: true }, 0.1);

Figure 12-4 shows a more complex example, hinting at the possibilities.

Figure 12-4. A demo showing a Shel Silverstein drawing being drawn onto the page while text is animated onto the page

Animate the Stroke!

With either CSS or DrawSVG, make sure the element has a stroke to begin with. I’ve seen students get stumped for a while because they didn’t create a stroke initially—there was nothing to animate, so nothing happened, but nothing “failed” either.

This applies to groups too. If you target a group, make sure to get the elements inside the group, not just the group itself. Even if the stroke is applied to the group and then cascades to the other paths/shapes, the group itself doesn’t have a stroke; only the elements do.

Working with DrawSVG is simple, and the multitude of ways to work with it make it really flexible and useful in a broad range of contexts.  

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.39.142