Skip to content

Commit

Permalink
Implement 'swiftclick-ignore' logic
Browse files Browse the repository at this point in the history
  • Loading branch information
munkychop committed Oct 29, 2015
2 parents 2c09f17 + 5ef4767 commit 4310615
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 48 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SwiftClick is a library created to eliminate the 300ms click event delay on touc
### Teeny-tiny
Less than 1kB bytes minified & gzipped :-)


## Usage

Firstly, grab either the [minified](https://raw.githubusercontent.com/munkychop/swiftclick/master/js/dist/swiftclick.min.js), or [non-minified](https://raw.githubusercontent.com/munkychop/swiftclick/master/js/libs/swiftclick.js) source from Github.
Expand All @@ -22,6 +23,7 @@ Or you can install via Bower instead, if that's your thing:
bower install swiftclick
```


### Include SwiftClick in your application
```html
<script type="application/javascript" src="path/to/swiftclick.min.js"></script>
Expand All @@ -33,6 +35,7 @@ If using CommonJS then simply require SwiftClick as per usual:
var SwiftClick = require("swiftclick");
```


### Setup SwiftClick

Setting up SwiftClick is a very easy process, which mirrors that of FastClick in that instances must be attached to a context element. Touch events from all elements within the context element are automatically captured and converted to click events when necessary, minus the delay.
Expand All @@ -50,6 +53,7 @@ var navigationSwiftClick = SwiftClick.attach(someNavElement);
var uiSwiftClick = SwiftClick.attach(someOtherElement);
```


### Default Elements
Once attached, by default SwiftClick will track events originating from the following element types:

Expand All @@ -67,6 +71,7 @@ var swiftclick = SwiftClick.attach(someElement);
swiftclick.addNodeNamesToTrack(["p", "h1", "nav"]);
```


### Replacing all stored node names to track

```js
Expand All @@ -77,6 +82,45 @@ swiftclick.replaceNodeNamesToTrack(["a", "div", "h1"]);
Doing this will remove all default node names, as well as any that have been added, and replace them with the node names within the array that is passed in, resulting in only the new node names being tracked.


### Ignoring swift clicks on specific elements

The parsing of CSS class names is disabled by default to improve performance, so in order to use this feature, it must be explicity switched on:

```js
var swiftclick = SwiftClick.attach(document.body);
swiftclick.useCssParser(true);
```

Adding the `swiftclick-ignore` class to an element will disable swift clicks on the element and all off its children:

```html
<div class="swiftclick-ignore">
This element and its children will not get swift clicks.
</div>
```


### Enabling swift clicks on specific elements within an ignored element

Turn on CSS class name parsing:

```js
var swiftclick = SwiftClick.attach(document.body);
swiftclick.useCssParser(true);
```

Within any element containing the `swiftclick-ignore` class, swift clicks can be enabled for specific child elements by adding the `swiftclick-force` class:

```html
<div class="swiftclick-ignore">
<button>First</button>
<button class="swiftclick-force">Second</button>
</div>
```

In this example, the first button will not get swift clicks, but the second button will.


### Automatically disabled when not needed
SwiftClick only intercepts events for touch devices that support orientation change, otherwise it just sits there looking pretty.

Expand Down
3 changes: 2 additions & 1 deletion css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ html, body
.test-element
{
display: block;
width: 200px;
width: 300px;
height: 200px;
margin: 40px auto 0 auto;
background-color: #000;
Expand All @@ -18,6 +18,7 @@ html, body
line-height: 200px;
font-family: Helvetica, Arial, sans-serif;
cursor: pointer;
white-space: normal;

-moz-user-select: -moz-none;
-khtml-user-select: none;
Expand Down
11 changes: 7 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
<link rel="stylesheet" type="text/css" href="css/app.css">
</head>

<body class="swiftclick-ignore">
<body>
<div class="wrapper">

<div class="test-element">Div</div>
<button class="test-element">Button</button>
<span class="test-element">Span</span>
<div class="test-element swiftclick-force">Div</div>

<div class="swiftclick-ignore">
<span class="test-element">Span - ignored</span>
<button class="test-element swiftclick-force">Button - forced</button>
</div>

<!-- SwiftClick works for anchor elements by default without the need for an additional JavaScript handler -->
<a class="test-element" name="test-anchor" href="#test-anchor">Anchor</a>
Expand Down
5 changes: 4 additions & 1 deletion js/app/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
(function ()
{
SwiftClick.attach (document.body);
"use strict";

var swiftclick = window.SwiftClick.attach(document.body);
swiftclick.useCssParser(true);

// add regular click listeners to all elements with a class of 'test-element'.
var testElements = document.getElementsByClassName("test-element"),
Expand Down
101 changes: 59 additions & 42 deletions js/libs/swiftclick.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @license MIT License (see license.txt)
*/

"use strict";

function SwiftClick (contextEl)
Expand All @@ -15,7 +15,8 @@ function SwiftClick (contextEl)
this.options =
{
elements: {a:"a", div:"div", span:"span", button:"button"},
maxTouchDrift: 30
maxTouchDrift: 30,
useCssParser: false
};

var _self = this,
Expand All @@ -24,7 +25,7 @@ function SwiftClick (contextEl)
_currentlyTrackingTouch = false,
_touchStartPoint = {x:0, y:0},
_scrollStartPoint = {x:0, y:0},
_shouldSynthesizeClickEvent = true,
//_shouldSynthesizeClickEvent = true,
_clickedAlready = false;


Expand All @@ -36,7 +37,6 @@ function SwiftClick (contextEl)

function init ()
{
// console.log("init");
// check if the swift el already has a click handler and if so hijack it so it get's fired after SwiftClick's, instead of beforehand.
if (typeof _swiftContextElOriginalClick === "function")
{
Expand All @@ -55,15 +55,11 @@ function SwiftClick (contextEl)

function touchStartHandler (event)
{
// console.log("touchStartHandler");
console.log("touchStartHandler");

var targetEl = event.target,
nodeName = targetEl.nodeName.toLowerCase(),
touch = event.changedTouches[0];

// store touchstart positions so we can check for changes later (within touchend handler).
_touchStartPoint.x = touch.pageX;
_touchStartPoint.y = touch.pageY;
_scrollStartPoint = getScrollPoint();

// don't synthesize an event if the node is not an acceptable type (the type isn't in the dictionary).
if (typeof _self.options.elements[nodeName] === "undefined")
Expand All @@ -79,24 +75,33 @@ function SwiftClick (contextEl)
}

// check parents for 'swiftclick-ignore' attribute.
if (checkIfElementShouldBeIgnored(targetEl))
if (_self.options.useCssParser && checkIfElementShouldBeIgnored(targetEl))
{
// _shouldSynthesizeClickEvent = false;
_clickedAlready = false;
return true;
}

event.stopPropagation();

_currentlyTrackingTouch = true;

// store touchstart positions so we can check for changes later (within touchend handler).
_touchStartPoint.x = touch.pageX;
_touchStartPoint.y = touch.pageY;
_scrollStartPoint = getScrollPoint();

console.log("touchstart:", event.changedTouches[0]);

// only add the 'touchend' listener now that we know the element should be tracked.
targetEl.removeEventListener("touchend", touchEndHandler, false);
targetEl.addEventListener("touchend", touchEndHandler, false);
}

function touchEndHandler (event)
{
// console.log("touchEndHandler");
console.log("[touchEndHandler] targetEl:", event.target);

var targetEl = event.target,
touchend,
allowFurtherEventsWhenCancellingSyntheticClick = true;
Expand All @@ -106,12 +111,13 @@ function SwiftClick (contextEl)
touchend = event.changedTouches[0];

// cancel the touch if the node type is unacceptable (not in the dictionary), or if the touchpoint position has drifted significantly.
if (!_shouldSynthesizeClickEvent ||
if (//!_shouldSynthesizeClickEvent ||
Math.abs(touchend.pageX - _touchStartPoint.x) > _self.options.maxTouchDrift ||
Math.abs(touchend.pageY - _touchStartPoint.y) > _self.options.maxTouchDrift ||
Math.abs(getScrollPoint().x - _scrollStartPoint.x) > _self.options.maxTouchDrift ||
Math.abs(getScrollPoint().y - _scrollStartPoint.y) > _self.options.maxTouchDrift)
{
console.log("cancelling click... touchend:", touchend);

// stop further events if we are already tracking.
if (_currentlyTrackingTouch)
Expand All @@ -123,7 +129,7 @@ function SwiftClick (contextEl)

// reset vars to default state before returning early, effectively cancelling the creation of a synthetic click event.
_currentlyTrackingTouch = false;
_shouldSynthesizeClickEvent = true;
//_shouldSynthesizeClickEvent = true;
return allowFurtherEventsWhenCancellingSyntheticClick;
}

Expand All @@ -136,16 +142,18 @@ function SwiftClick (contextEl)
targetEl.focus();
synthesizeClickEvent(targetEl, touchend);

// reset vars to default state before returning early, effectively cancelling the creation of a synthetic click event.
// reset vars to default state.
_currentlyTrackingTouch = false;
_shouldSynthesizeClickEvent = true;
//_shouldSynthesizeClickEvent = true;

// return false in order to surpress the regular click event.
return false;
}

function clickHandler (event)
{
// console.log("clickHandler");
console.log("clickHandler");

var targetEl = event.target,
nodeName = targetEl.nodeName.toLowerCase();

Expand Down Expand Up @@ -193,24 +201,28 @@ function SwiftClick (contextEl)
function checkIfElementShouldBeIgnored (el)
{
var classToIgnore = "swiftclick-ignore";
var classToForceClick = "swiftclick-force";
var parentEl = el.parentNode;
var shouldIgnoreElement = false;

// return if the el itself has the 'swiftclick-ignore' class.
// ignore the target el and return early if it has the 'swiftclick-ignore' class.
if (hasClass(el, classToIgnore)) return true;

var parentEl = el.parentNode;
var shouldIgnoreElement = false;
// don't ignore the target el and return early if it has the 'swiftclick-force' class.
if (hasClass(el, classToForceClick)) return shouldIgnoreElement;

// the topmost element has been reached.
if (parentEl === null)
{
return shouldIgnoreElement;
}

// ignore the target el if one of its parents has the 'swiftclick-ignore' class.
while (parentEl) {

if (hasClass(parentEl, classToIgnore))
{
// console.log("el should be ignored due to el:", parentEl);
console.log("el should be ignored due to el:", parentEl);

parentEl = null;
shouldIgnoreElement = true;
Expand All @@ -229,34 +241,39 @@ function SwiftClick (contextEl)
function hasClass (el, className) {

var classExists = typeof el.className !== "undefined" ? (" " + el.className + " ").indexOf(" " + className + " ") > -1 : false;
// console.log("[hasClass] class exists?", classExists, "className?", typeof el.className !== "undefined");

return classExists;
}
}

// add an array of node names (strings) for which swift clicks should be synthesized.
_self.addNodeNamesToTrack = function (nodeNamesArray)
{
var i = 0,
length = nodeNamesArray.length,
currentNodeName;

for (i; i < length; i++)
{
if (typeof nodeNamesArray[i] !== "string") throw new TypeError ("all values within the 'nodeNames' array must be of type 'string'");
SwiftClick.swiftDictionary = {};

currentNodeName = nodeNamesArray[i].toLowerCase();
_self.options.elements[currentNodeName] = currentNodeName;
}
};
// add an array of node names (strings) for which swift clicks should be synthesized.
SwiftClick.prototype.addNodeNamesToTrack = function (nodeNamesArray)
{
var i = 0,
length = nodeNamesArray.length,
currentNodeName;

_self.replaceNodeNamesToTrack = function (nodeNamesArray)
for (i; i < length; i++)
{
_self.options.elements = {};
_self.addNodeNamesToTrack(nodeNamesArray);
};
}
if (typeof nodeNamesArray[i] !== "string") throw new TypeError ("all values within the 'nodeNames' array must be of type 'string'");

SwiftClick.swiftDictionary = {};
currentNodeName = nodeNamesArray[i].toLowerCase();
this.options.elements[currentNodeName] = currentNodeName;
}
};

SwiftClick.prototype.replaceNodeNamesToTrack = function (nodeNamesArray)
{
this.options.elements = {};
this.addNodeNamesToTrack(nodeNamesArray);
};

SwiftClick.prototype.useCssParser = function (useParser)
{
this.options.useCssParser = useParser;
};

// use a basic implementation of the composition pattern in order to create new instances of SwiftClick.
SwiftClick.attach = function (contextEl)
Expand Down

0 comments on commit 4310615

Please sign in to comment.