In one of our recent projects, we had to devise a solution for the virtual visitors of a large shopping mall, which has an overwhelming diversity of businesses, brands, activities and services. What we needed was a surface map that would interact with the user, so that they could search for points of interest, be taken there and shown key information on the fly. The source serving the map had to be dynamic and thus would be changed frequently and updated as a ‘dumb’ SVG file; the live map would have to adapt to the changes.
If you face a similar challenge, this article takes you through the steps of building a basic JavaScript plugin for interacting with an element which allows zooming and panning using your mouse. We will be using the Revealing Module design pattern (RMP) to write our plugin. (check this out if you want to read more on RMP).
Step 0 – Basic Prerequisites
Let’s call our plugin Moveit and get the HTML & CSS out of the way.
We need a fresh HTML document, in which to put 3 things: a button, a container that can be anything (in our case a <figure>) and a target that can also be anything (in our case an <img>):
<!DOCTYPE html>
<html lang="en">
<head>
<title>Moveit</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<button>Reset</button>
<figure class="box">
<img src="funny.jpg" alt="Funny Whale Shark">
</figure>
</body>
</html>
Now the CSS, to style our box so that we can see our context better, and the button:
.box {
width: 50%; margin: 10px auto;
padding: 50px 0;
border: 1px solid #dedede;
overflow: hidden;
text-align:
center;
background-color: #eee;
}
button
{
display: block;
margin: 0 auto;
padding: 10px;
background-color: #eee;
border: 1px solid #ccc;
}
Step 1 – Initial Settings and Targets
We start our plugin by assigning a self-invoking function to an aptly named variable in which to hold all our logic and expose only what we need. Let’s create a moveit.js file and start writing:
var moveit = (function()
{
// code here
})();
Now we want to build and hold our initial settings; we do this using an object literal. We will be using CSS3 transformations to manipulate our target, so we need to establish max and min values for the scale (zooming) and a step (how much we zoom at a time), to decide if transitions are allowed and choose the transition string. We then update moveit.js inside our function with:
var settings = {
zoomMin: 0.5,
zoomMax: 2,
zoomStep: 0.2, // How much one step zooms
transitionAllowed: true,
transitionString: 'transform 0.4s ease-out'
};
We also need to hook our target and our button (which we will use to reset the target). Here I decided to go with HTML5 data-attributes for selectors, since I consider them easier to read in the code and less intrusive. Depending on your needs, you might want to go for classes or ids. So we first update our HTML, adding the data-attributes. I went with “data-mi-target” for the target and “data-mi-reset” for the button:
<button data-mi-reset data-mpz-reset>Reset</button>
<figure class="box">
<img data-mi-target src="funny.jpg" alt="Funny Whale Shark">
</figure>
We’re not interested in attribute values, we just want them present. Now let’s select them – update moveit.js, bellow the “settings” variable, like this:
var targetElement = document.querySelector( '[data-mi-target]' ),
resetTrigger = document.querySelector( '[data-mi-reset]' );
So far, so good. Before we move on, though, let’s also include our new script in the HTML code, just before the tag:
<script src="moveit.js"></script>
Step 2 – Reference Points and Apply/Get Methods
We will be moving things around, so we need to get some reference points for positions and transformations. Add the following code to moveit.js:
var ref =
{ // Reference points, null at first
x: null, // X Axis
y: null // Y Axis
};
var transforms = { // Transforms state – CSS default values
scale: 1,
translateX: 0,
translateY: 0
};
var intialTransforms = JSON.stringify( transforms );
// Making a copy of the initial state for later
Cool. Now let’s build methods that allow us to override default settings, see the settings object and apply transformations. In moveit.js we write:
var applySettings = function ( newSettings )
{
// We want to pass in an object literal for settings
if (typeof newSettings === 'object')
{
// We check our argument is an object
Object.keys( newSettings ).forEach( function ( option )
{
// Now get new values in a forEach loop
settings[ option ] = newSettings[ option ];
}
);
} else console.log('Wrong settings type!');
};
var getSettings = function ()
{
// a simple method to double-check settings, handy for debugging
console.log( settings );
};
Awesome! Now we need a method to call when we have some transform values and we want to apply them. We do that by going over the “transforms” object after updating it (we will write this later) and translating the objects pairs into CSS3 transforms. Add to moveit.js:
var applyTransforms = function ()
{
targetElement.style.transform = Object.keys( transforms ).map( function ( t )
{
return t + '(' + transforms[ t ] + ')';
} ).join( ' ' ); // we loop over “transforms” and build a string which we will pass to the style.transform property
};
Step 3 – Zooming, Moving and Reset
It’s time for the good bits. First is zooming – we’ll build a function which will be called with a mouse scroll event, determine the scrolling direction using the events “deltaY” property and apply the transformation using our defined “settings.zoomStep” value. We also need to check min and max zoom values. Add to moveit.js:
var zoom = function ( event )
{
event.preventDefault();
// We don’t want scrolling to happen, we just need the scroll direction
if ( settings.transitionAllowed )
{
// Checking to see if transitions are allowed
targetElement.style.transition = settings.transitionString;
}
if ( event.deltaY < 0 && transforms.scale < settings.zoomMax )
{
// Negative deltaY means we scrolledup, so we need to zoom in
transforms.
scale += settings.zoomStep;
// Add one step of zoom for every scroll
applyTransforms();
// Apply transforms using new values
}
else if ( event.deltaY > 0 && transforms.scale > settings.zoomMin )
{
// Same as before but with positive deltaY, means we zoom out transforms.
scale -= settings.zoomStep; applyTransforms();
}
};
Ok, let’s tackle moving next. We need multiple methods for this, so let’s start with a function which will take reference points at mouse click. Add to moveit.js:
var mouseDown = function ( event )
{
var cx = event.pageX, // Get X coordinate of the click
cy = event.pageY; // Get Y coordinate of the click
targetElement.style.transition = 'none';
// Remove any present transitions, we don’t want them here
ref.x = cx - parseInt( transforms.translateX ) * transforms.scale; // Use the event position to set reference points and
ref.y = cy - parseInt( transforms.translateY ) * transforms.scale; // factor in existing transforms (translation and scale)
window.addEventListener( 'mousemove', move );
// Now listen for movement
};
Now we need to create the “move” function, which we attached to the “mousemove” event earlier. This function will track our mouse movement (while holding down the click) and translate it into transformations. Add to moveit.js:
var move = function ( event )
{
event.preventDefault();
var cx = event.pageX,
cy = event.pageY; // We track our position on screen
targetElement.style.pointerEvents = 'none';
// The element we’re moving might react to clicks, so we need to cancel that while moving
transforms.translateX = ( cx - ref.x ) / transforms.scale + 'px'; // Calculate axis movement by subtracting the reference positions from
transforms.translateY = ( cy - ref.y ) / transforms.scale + 'px'; // our current positions
applyTransforms(); // Apply transforms using new values
};
Next, we need a function to restore reaction to our element and remove the listener which calls our “move” function. Add to moveit.js:
var mouseUp = function ()
{
targetElement.style.pointerEvents = 'auto';
// Restore pointer events, so the element can react again
window.removeEventListener( 'mousemove', move );
// Remove movement listener
};
Voila! We have movement. Of course, nothing actually happens yet, because we’re not listening for events, so don’t panic. Next, we need a simple function that will reset our transforms. Add to moveit.js:
var reset = function ()
{
if ( settings.transitionAllowed )
{
// Transition check
targetElement.style.transition = settings.transitionString;
}
transforms = JSON.parse( intialTransforms );
// Remember that copy of transforms we made at the beginning ?
applyTransforms();
};
Goody! We’re almost done. All that’s left is listening for events and exposing a couple of our methods so that we can call them from the outside when needed. First, we check if all the elements we need are present and if they are we add in their listeners. Add to moveit.js:
if ( targetElement )
{
// Check if a target exists
targetElement.addEventListener( 'wheel', zoom );
// Listen for mouseweheel, and call zoom function
targetElement.addEventListener( 'mousedown', mouseDown );
// Listen for mouse down, and call mouseDown function
window.addEventListener( 'mouseup', mouseUp );
// Listen for mouse up, and call mouseUp function
}
if ( resetTrigger )
{
// Check if a reset button exists
resetTrigger.addEventListener( 'click', reset );
}
Awesome! Now, if everything was done right, we can move and interact with our element. Lastly, we expose a couple of methods, specifically applySettings and getSettings, so we can override and check default settings if we need to. Add to moveit.js:
return {
applySettings: applySettings,
// we’re actually returning references to our methods
getSettings: getSettings
};
Source: fortech
The Tech Platform
Comments