Using touch in Hilo (Windows Store apps using JavaScript and HTML)
[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]
From: Developing an end-to-end Windows Store app using JavaScript: Hilo
Hilo provides examples of tap, slide, swipe, pinch and stretch, and turn gestures. Here we explain how we applied the Windows 8 touch language to Hilo to provide a great experience on any device.
Download
After you download the code, see Getting started with Hilo for instructions.
You will learn
- How the Windows 8 touch language was used in Hilo.
Applies to
- Windows Runtime for Windows 8
- Windows Library for JavaScript
- JavaScript
Part of providing a great experience is ensuring that an app is accessible and intuitive to use on a traditional desktop computer and on a small tablet. For Hilo, we put touch at the forefront of our UX planning because it adds an important experience by providing a more engaging interaction between the user and the app.
As described in Designing the UX (Hilo C++), touch is more than simply an alternative to using a mouse. We wanted to make touch an integrated part of the app because touch can add a personal connection between the user and the app. Touch is also a natural way to enable users to crop and rotate their photos. In addition, we use Semantic Zoom to highlight how levels of related complexity can easily be navigated. When the user uses the pinch gesture on the month page, the app switches to a calendar-based view. Users can then browse photos more quickly.
Hilo uses the Windows 8 touch language. We use the standard touch gestures that Windows provides for these reasons:
- The Windows Runtime provides an easy way to work with them.
- We don't want to confuse users by creating custom interactions.
- We want users to use the gestures that they already know to explore the app, and not need to learn new gestures.
We also wanted Hilo to be intuitive for users who use a mouse or similar pointing device. The built-in controls work as well with a mouse or other pointing device as they do with touch. So when you design for touch, you also get mouse and pen functionality. For example, you can use the left mouse button to invoke commands. In addition, mouse and keyboard equivalents are provided for many commands. For example, you can use the right mouse button to activate the app bar, and holding the Ctrl key down while scrolling the mouse wheel controls Semantic Zoom interaction.
The document Touch interaction design explains the Windows 8 touch language. The following sections describe how we applied the Windows 8 touch language to Hilo.
[Top]
Tap for primary action
Tapping an element invokes its primary action. For example, in crop mode, you tap the crop area to crop the image.
To implement crop, we used a canvas. The click event of the canvas initiates the crop operation, with the event firing for both pointer devices and touch. In the ready function of the page control, crop.js, the setupImageView function is called. This function creates an instance of the ImageView class. Here's the code.
Hilo\Hilo\crop\setupImageView.js
this.imageView = new Hilo.Crop.ImageView(image, this.cropSelection, canvasEl, imageEl);
We added a listener in the ImageView constructor. The listener specifies the function that executes when a click is received on the canvas. WinJS.Class.mix is used to define the ImageView class. The eventMixin mixin is used.
Hilo\Hilo\crop\imageView.js
canvasEl.addEventListener("click", this.click.bind(this));
The click function simply raises the preview event.
Hilo\Hilo\crop\imageView.js
this.dispatchEvent("preview", {});
We added a listener in the start function in the CropPresenter class. This listener specifies the function that executes when the preview event is received from the ImageView class.
Hilo\Hilo\crop\cropPresenter.js
this.imageView.addEventListener("preview", this.cropImage.bind(this));
The cropImage function calls the cropImage function of the ImageView class. This function obtains the canvas-based crop selection, and then the selected area of the original image is calculated. We perform this calculation by scaling the canvas-based selection to the original image dimensions. The on-screen image is then cropped, to show what the crop result will look like when the file is saved. To support multiple cropping operations, we store an offset that represents the starting location of the crop on the original image, rather than relative to the canvas size. Here's the code.
Hilo\Hilo\crop\imageView.js
cropImage: function () {
var selectionRectScaledToImage = this.getScaledSelectionRectangle();
// Reset image scale so that it reflects the difference between
// the current canvas size (the crop selection) and the original
// image size, then re-draw everything at that new scale.
this.imageToScreenScale = this.calculateScaleToScreen(selectionRectScaledToImage);
this.drawImageSelectionToScale(selectionRectScaledToImage, this.imageToScreenScale);
// Remember the starting location of the crop on the original image
// and not relative to the canvas size, so that cropping multiple times
// will correctly crop to what has been visually selected.
this.image.updateOffset({ x: selectionRectScaledToImage.startX, y: selectionRectScaledToImage.startY });
return this.canvasEl.toDataURL();
},
[Top]
Slide to pan
Hilo uses the slide gesture to pan among images in a collection. For example, when you view an image, you can also quickly pan to any image in the current collection by using the FlipView, or by using the filmstrip that appears when you display the app bar. We used the ListView control to implement the filmstrip.
A benefit of using the ListView object is that it has touch capabilities built in, removing the need for additional code.
[Top]
Swipe to select, command, and move
With the swipe gesture, you slide your finger perpendicular to the panning direction to select objects. In Hilo, when a page contains multiple images, you can use this gesture to select one image. When you display the app bar, the commands that appear apply to the selected image. ListView and other controls provide built-in support for selection. You can use the selection property to retrieve the selected item.
[Top]
Pinch and stretch to zoom
Pinch and stretch gestures are not just for magnification, or performing "optical" zoom. Hilo uses Semantic Zoom to help users navigate between large sets of pictures. Semantic Zoom enables you to switch between two different views of the same content. You typically have a main view of your content and a second view that allows users to quickly navigate through it. (Read Quickstart: adding a SemanticZoom for more info about Semantic Zoom.)
On the month page, when you zoom out, the view changes to a calendar-based view. The calendar view highlights those months that contain photos. You can then zoom back in to the image-based month view. The following diagram shows the two views that the Semantic Zoom switches between.
To implement Semantic Zoom, we used the SemanticZoom object. We then provide controls for the zoomed-in and zoomed-out views. The controls must implement the IZoomableView interface. Hilo uses the ListView control to implement Semantic Zoom.
Note The WinJS provides one control that implements IZoomableView—the ListView control. You can also create your own custom controls that implement IZoomableView or augment an existing control to support IZoomableView so that you can use it with SemanticZoom.
For the zoomed-in view, we display a ListView that binds to photo thumbnails that are grouped by month. The ListView also shows a title (the month and year) for each group. For more info about the zoomed-in view implementation, see Working with data sources.
For the zoomed-out view, we display a ListView that binds to photo thumbnails that are grouped by year. Here's the HTML for the zoomed-out view.
Hilo\Hilo\month\month.html
<div id="yeargroup" data-win-control="Hilo.month.YearList">
</div>
The DIV element specifies a data-win-control attribute that has a value of Hilo.month.YearList. This sets the DIV element as the host for the YearList control. The YearList class, which implements the control, creates and initializes a new ListView object. Here's the code.
Hilo\Hilo\month\yearList.js
var listViewEl = document.createElement("div");
element.appendChild(listViewEl);
var listView = new WinJS.UI.ListView(listViewEl, {
layout: { type: WinJS.UI.GridLayout },
selectionMode: "none",
tapBehavior: "none"
});
this.listView = listView;
listView.layout.maxRows = 3;
The YearList class also creates a template to format and display the multiple instances of data in the year group. We create this template by setting the itemTemplate property of the ListView to a function that generates DOM elements for each item in the year group.
For more info about setting up a data source, see Working with data sources.
For more info about Semantic Zoom, see Quickstart: adding a SemanticZoom and Guidelines for Semantic Zoom.
[Top]
Turn to rotate
In the rotate image view, you can use two fingers to rotate the image. When you release your fingers, the image snaps to the nearest 90-degree rotation.
The ready function of the page control class for the rotate page, rotate.js, creates a new instance of the TouchProvider class. Here's the code.
Hilo\Hilo\rotate\rotate.js
var touchProvider = new Hilo.Rotate.TouchProvider(element);
The TouchProvider class provides logic to show the photo rotating as the rotate gesture is being applied. The constructor creates an instance of the GestureRecognizer class, which listens for and handles all pointer and gesture events. The gesture recognizer is configured to only process the rotate gesture. Event listeners are then registered for the MSPointerDown, MSPointerMove, and MSPointerUp DOM pointer events, along with the manipulationupdated and manipulationcompleted events. Functions are registered for these DOM pointer events, which simply pass the received PointerPoint to the gesture recognizer.
Hilo\Hilo\rotate\TouchProvider.js
function TouchProviderConstructor(inputElement) {
var recognizer = new Windows.UI.Input.GestureRecognizer();
recognizer.gestureSettings = Windows.UI.Input.GestureSettings.manipulationRotate;
this._manipulationUpdated = this._manipulationUpdated.bind(this);
this._manipulationCompleted = this._manipulationCompleted.bind(this);
inputElement.addEventListener("MSPointerDown", function (evt) {
var pp = evt.currentPoint;
if (pp.pointerDevice.pointerDeviceType === pointerDeviceType.touch) {
recognizer.processDownEvent(pp);
}
}, false);
inputElement.addEventListener("MSPointerMove", function (evt) {
var pps = evt.intermediatePoints;
if (pps[0] && pps[0].pointerDevice.pointerDeviceType === pointerDeviceType.touch) {
recognizer.processMoveEvents(pps);
}
}, false);
inputElement.addEventListener("MSPointerUp", function (evt) {
var pp = evt.currentPoint;
if (pp.pointerDevice.pointerDeviceType === pointerDeviceType.touch) {
recognizer.processUpEvent(pp);
}
}, false);
recognizer.addEventListener("manipulationupdated", this._manipulationUpdated);
recognizer.addEventListener("manipulationcompleted", this._manipulationCompleted);
this.displayRotation = 0;
},
The manipulationupdated and manipulationcompleted events are used to handle rotation events. The _manipulationUpdated method updates the current rotation angle and the _manipulationCompleted method snaps the rotation to the nearest 90-degree value. Here's the code.
Hilo\Hilo\rotate\TouchProvider.js
_manipulationUpdated: function (args) {
this.setRotation(args.cumulative.rotation);
},
_manipulationCompleted: function (args) {
var degrees = args.cumulative.rotation;
var adjustment = Math.round(degrees / 90) * 90;
this.animateRotation(adjustment);
}
Caution These event handlers are defined for the entire page. If your page contains more than one item, you need additional logic to determine which object to manipulate.
The page control class for the rotate page, rotate.js, passes the instance of the TouchProvider class to the RotatePresenter class, where the setRotation and animateRotation methods of the TouchProvider class are set to internal methods in the RotatePresenter class.
Hilo\Hilo\rotate\rotatePresenter.js
touchProvider.setRotation = this._rotateImageWithoutTransition;
touchProvider.animateRotation = this._rotateImage;
Calls in the TouchProvider class to the setRotation and animateRotation methods call the _rotateImageWithoutTransition and _rotateImage internal methods of the RotatePresenter class respectively, in order to display the image rotated on-screen by setting the CSS rotation of the image element.
For more info, see Quickstart: Pointers, Quickstart: DOM gestures and manipulations, Quickstart: Static gestures, and Guidelines for rotation.
[Top]
Swipe from edge for app commands
When there are relevant commands to display, Hilo displays the app bar when the user swipes from the bottom or top edge of the screen.
Every page can define a navigation bar, a bottom app bar, or both. For instance, Hilo displays both when you view an image and activate the app bar.
Here's the navigation bar.
Here's the bottom app bar for the same photo.
The location of the app bar on a page is controlled by its placement property.
Hilo implements an image navigation control that provides a re-usable implementation of the bottom app bar that can be used to navigate to the rotate and crop pages. There are two parts to this control:
- The hiloAppBar.html file, which can be included in any page that needs the navigation app bar.
- The hiloAppBarPresenter.js file, which is the controller that's used to provide the functionality of the app bar.
This control is used on the hub, month, and detail pages. To add the control, we add a reference to it in the page's markup and in the page control file. Here's the code for adding it to a page's markup.
Hilo\Hilo\detail\detail.html
<section id="image-nav" data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/Hilo/controls/HiloAppBar/hiloAppBar.html'}"></section>
In the page control file, the HiloAppBarPresenter requires a reference to the HTML element that's used to place the control on the screen. Here's the code.
Hilo\Hilo\detail\detail.js
var hiloAppBarEl = document.querySelector("#appbar");
var hiloAppBar = new Hilo.Controls.HiloAppBar.HiloAppBarPresenter(hiloAppBarEl, WinJS.Navigation, query);
Caution In most cases, you shouldn't programmatically display an app bar if there are no relevant commands to show. For example, the main hub page shows the app bar whenever a photo is selected by calling the HiloAppBarPresenter.setNavigationOptions method. Calling the HiloAppBarPresenter.clearNavigationOptions method will hide the app bar.
For more info about app bars, see Adding app bars.
[Top]
Swipe from edge for system commands
Because touch interaction can be less precise than other pointing devices, such as a mouse, we maintained a sufficient distance between the app controls and the edges of the screen. Because we maintained this distance, the user can easily swipe from the edge of the screen to reveal the app bars and charms, or to display previously used apps. For more info see Laying out an app page.
[Top]