Creating interactive infographics with plain Javascript (Part-two) Navigation UI for large canvases Recap This article is a five-part series on creating interactive infographics with plain Javascript. So far, we’ve designed a schema and a view engine. However, the usability of an infographics design is still limited by the size of the browser window. To support a larger canvas, we can find ready answers from native Javascript. Objective Let’s create navigation features to browse a large canvas. Introduction Think of infographics as a forest. With a wealth of information to present, designers may be tempted to squeeze the entire forest into a browser window. Texts, shapes, and images become too meshed up for the human eyes to see comfortably. Going the other direction, designers may instead slice a layout into smaller parts and present each as separate pages. Visitors may see the trees but lose the forest. Is it possible to see both the trees and the forest at the same time? Why not let users adjust the level of details as needed? Concept Imagine holding a “looking glass” over a map. Instead of shifting the looking glass to examine different parts of the map, we move the map itself. This effect is achieved through a CSS feature called . By manipulating its properties, we can create 4 powerful navigation effects: overflow Scroll (by X and Y scrollbar) Pan (by cursor’s relative position) Grab and drag (by cursor’s movement) Zoom (in or out) Getting started—prepping the canvas Create a variable called to reference the map. This is the parent container. canvas var canvas = document.getElementById(“parent ”); Container Scroll Scroll vertically and horizontally The first technique is created with a simple CSS: scroll div {overflow:scroll;} controls how content is displayed. Think of this property as an imaginary overlay with a cutout window in its middle. is our virtual looking glass. overflow overflow Beneath the glass is the forest (i.e. ) itself. tells the browser to move the underlying canvas horizontally or vertically (or both via a trackpad). Only exposed parts of the forest can be observed through that window cutout. canvas scroll Preventing accidental interaction We don’t want users to unintentionally move the when they are just browsing something else. You can prevent that with a basic HTML button to toggle between the UI states of and . You’ll probably want to be the default CSS behaviour (i.e. lock X and Y scroll). canvas scroll hidden hidden canvas.style.overflow = "hidden"; When visitors are ready to explore the , let them toggle to . canvas scroll canvas.style.overflow = "scroll"; is a default browser feature. You won’t have to write any custom handlers. scroll Let’s move on to the next navigation feature. Pan Pan by holding down the right mouse button and moving the mouse simultaneously Regardless of size, it is possible to explore an entire canvas quickly without running off the mousepad. This feature is activated by holding down the right mouse button and moving the mouse simultaneously. canvas Use the position of the mouse cursor to calculate the new relative scroll value (see bold). canvas.addEventListener("mousemove", handlerMove, false);function handlerMove(event) {if (event.which == 3) { // use right button to pan }} canvas.scrollTo( event.clientX , event.clientY ); listens to any mouse buttons being pressed (and not released). mousemove detects the the right mouse button. detects the left. detects the middle scroll button. event.which == 3 event.which == 1 event.which == 2 and provides the current coordinates of the mouse cursor. event.clientX event.clientY . Let’s reserve the right mouse button for “panning” navigation. As the browser shows a contextual menu by default for the right mouse button, we’ll need to instruct the browser to give us control like so: Setting up the mouse buttons canvas.addEventListener(‘contextmenu’,function(event) {event.preventDefault();},false); Grab and drag Two concepts work in tandem to create this effect. . Let’s place the on a tabletop and imagine the mouse cursor as our finger. When we hold a finger against the canvas and push it around, our finger is said to have moved a certain X and Y distance. Similarly, the canvas moves by the same X and Y distance. The finger has caused an The is by how much the canvas has also moved. Action and reaction canvas action. reaction mirrored We want to this distance as a reaction on the canvas. In other words, the canvas should move by exactly how much the cursor has moved. Let’s call this value the mouse cursor delta. The mouse cursor delta. mirror Calculate with a surprisingly simple formula: start coordinates - end coordinates The result should update to a new scroll value in real time. Looking through our imaginary window cutout, a visitor would feel as if she’d dragged the canvas with her mouse cursor. Tip: “mouse delta value” is just a label and is not the same as the _WheelEvent.deltaX_ browser event. . Add an event listener to detect the pressing of the left-mouse button. Implementation mousedown canvas.addEventListener("mousedown", handlerGrab, false); function handlerGrab(event) {if (event.which == 1) {mouseDownBoolean = true;// Find the initial scroll value// Capture the initial mouse cursor position...};} assign the left mouse button with . event.which == 1 tells other related functions that the left-mouse button is currently down or not. mouseDownBoolean Add the and listeners in tandem to drag and release the canvas. mousemove mouseup canvas.addEventListener("mousemove", elementDrag, false); function elementDrag(event) {if (mouseDownBoolean){// Calculate the delta after the mouse cursor has moved// scroll to the new position...}}; canvas.addEventListener("mouseup", elementDragclose, false); function elementDragclose() {if (mouseDownBoolean){mouseDownBoolean = true; // "releases" the drag// change the cursor icon dynamically}}; triggers a custom function to drag the canvas. mousemove elementDrag qualifies the action only if the left mouse button is also held-down (as set by ). mouseDownBoolean handlerGrab detects the end of a and call to reset the UI state. mouseup three-step sequence elementDragclose Detecting all three actions as a sequence: 1. Press & hold the left mouse button2. Grab and drag the cursor some X and Y distances3. Release the mouse button Within the code block , we can find the start scroll value with : if (event.which == 1) {…} getBoundingClientRect() var distanceToTop = canvas.getBoundingClientRect().top;var distanceToLeft = canvas.getBoundingClientRect().left; and capture the initial mouse cursor position (before it moves): var posXdelta = 0, posYdelta = 0, posX = 0, posY = 0; myBox = e || window.event;posX = myBox.clientX - distanceToLeft;posY = myBox.clientY - distanceToTop; Within the code block find the end coordinates and calculate the resultant delta value: elementDrag(event), posXdelta = pos3 - ( myBox.clientX - distanceToLeft );posYdelta = pos4 - ( myBox.clientY - distanceToTop ); and command the browser to “scroll” to its new relative position: var newX = canvas.scrollLeft + posXdelta;var newY = canvas.scrollTop + posYdelta;canvas.scrollTo( newX , newY ); Manage your variables , , , , , , such that the above mentioned functions can access them. mouseDownBoolean pos1 pos2 pos3 pos4 distanceToTop distanceToLeft Tip: If you are showing a node element as an image with the _<img>_ tag, then prevent it from being accidentally dragged out of the window. itemElementName[i].ondragstart = function(){ return false; }; Don’t leave out the minor details Wouldn’t it feel weird to drag something with a pointy arrow cursor? How about changing it to a cursor (i.e. hand icon) within the code block ? grab elementDrag(event) canvas.style.cursor = "-webkit-grab";// supports Chrome, Safari and Opera Better still, add an animation effect whenever the mouse button is being pressed to show “grabbing in action”. grabbing canvas.style.cursor = "-webkit-grabbing"; Remember to reset the UI state at code block as soon as the mouse button is released. elementDragclose() Zoom Zoom with mouse-scroll button Use the scroll button for zooming. To zoom in and see the details, scroll up. To zoom out and see the bigger picture, scroll down. Use like so: wheel canvas.addEventListener("wheel", handlerWheel); by manipulating the numerical value. zoom canvas.style.zoom = 1; Here’s the structure: canvas.addEventListener("wheel", handlerWheel); function handlerWheel(event) { if (zoomAllow) { if (event.wheelDelta === 100) { zoomLevel = zoomLevel + 0.1; if (zoomLevel > 3){ ... } } else if (event.wheelDelta === -100) { zoomLevel = zoomLevel - 0.1; if (zoomLevel < 0.3){ ... } } } } It’s a good practice to prevent accidental interaction. We should also differentiate window actions from actions. Use a boolean to toggle between zoom and scroll “mode”. wheel scroll zoom zoomAllow Use to set the threshold for counting a zoom. event.wheelDelta Maintain a variable to zoom incrementally and smoothly. The smaller the value, the finer the zoom. Decimals are accepted. Consider adding an easing behaviour to animate smoothly. zoomLevel Use a conditional statement to set a max and min zoom range. if (zoomLevel > myNumber) . The absolute X and Y scroll value will remain the same after a zoom. This will “jump” the canvas to a new relative scroll position with every zoom. Let’s write a method to “remember” the original focus area. Maintaining focus var prevRatioX = (canvas.scrollLeft) / (canvas.scrollWidth - canvas.clientWidth);var prevRatioY = (canvas.scrollTop) / (canvas.scrollHeight - canvas.clientHeight); var newX = prevRatioX * (canvas.scrollWidth - canvas.clientWidth);var newY = prevRatioY * (canvas.scrollHeight - canvas.clientHeight) canvas.scrollTo( newX , newY ); and calculates the scroll ratio (before a zoom). prevRatioX prevRatioY and recalculates the new relative scroll values (after a zoom). newX newY tells the browser to go back to the previous relative position. scrollTo Next Steps We have enhanced the view engine with 4 navigation features. Let’s take it to the next level with a navigation gadget. Links to other parts sets the foundation for designing interactive infographics. Part-one Part-two → You’re now here. adds a dynamic mini-map to enhance navigation. Part-three adds an inline UI to access layered content. Part-four demonstrates why it is so easy to create UIs with a human touch. Part-five If you enjoyed this story, you can find more at . Pageii Studio