Skip to content

Instantly share code, notes, and snippets.

@DeCarabas
Created May 25, 2014 14:48
Show Gist options
  • Save DeCarabas/24694bc8cb7402c246f5 to your computer and use it in GitHub Desktop.
Save DeCarabas/24694bc8cb7402c246f5 to your computer and use it in GitHub Desktop.
javascript gallery swipe-right rocket surgery
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
<LINK REL="stylesheet" TYPE="text/css" HREF="dnalounge.css?3">
<TITLE>DNA Lounge: Anti-Nowhere League, 15 May 2014</TITLE>
<!-- %%HEAD_START%% -->
<STYLE TYPE="text/css">
<!--
.page { max-width: 100%; }
-->
</STYLE>
<!-- %%HEAD_END%% -->
<meta name="geo.position" content="37.771007;-122.412694">
<meta name="viewport" content="width=device-width, minimal-ui, minimum-scale=1.0, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
<meta property="og:type" content="article">
<meta property="fb:page_id" content="12161711085">
<meta property="fb:app_id" content="72575175403">
<meta name="twitter:creator" content="@dnalounge">
<meta name="twitter:site" content="@dnalounge">
<link rel="me" href="https://www.facebook.com/dnalounge">
<link rel="me" href="https://plus.google.com/118126466483537376327">
<link rel="publisher" href="https://plus.google.com/118126466483537376327">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="top" href="../../../">
<link rel="up" href="./#anti_nowhere_league">
<meta name="twitter:card" content="photo">
<meta name="description" content="DNA Lounge: Anti-Nowhere League, 15 May 2014">
<meta name="medium" content="image">
<meta property="og:image" content="http://www.dnalounge.com/gallery/2014/05-15/001.jpg">
<link rel="image_src" href="http://www.dnalounge.com/gallery/2014/05-15/001.jpg">
<link rel="next" href="002.html">
<link rel="last" href="105.html">
<SCRIPT LANGUAGE="javascript" SRC="../../gallery.js?1"></SCRIPT>
<script src="hand.js"></script>
<script src="dragnav.js"></script>
</HEAD>
<BODY>
<DIV ID="outerContainer" STYLE="overflow-x: hidden;">
<DIV ID="previewContainer" STYLE="position: relative;">
<DIV ID="thisPage" CLASS="page">
<DIV CLASS="top">
<!-- %%MENU_START%% -->
<DIV CLASS="masthead"><A HREF="../../../"><IMG SRC="logo.gif"></A></DIV>
<UL CLASS="menu" ID="menu1"><LI>
<A HREF="../../../">Home</A>
</LI><LI><A HREF="../../../calendar/latest.html">Calendar</A>
</LI><LI><A HREF="../../../directions/">Directions</A>
</LI><LI><A HREF="../../../tickets/">Tickets</A>
</LI><LI><A HREF="../../../webcast/">Webcasts</A>
</LI><LI><A HREF="../../../contact/">Contact</A>
</LI></UL>
<!-- %%MENU_END%% -->
</DIV>
<DIV CLASS="bottom">
<!-- %%BOTTOM_START%% -->
<DIV ALIGN=CENTER>
<DIV CLASS="top">
<DIV CLASS="gwbox">
<SPAN CLASS="navL">&lt;&lt;</SPAN>
<A HREF="./#anti_nowhere_league">Anti-Nowhere League</A>
<A HREF="002.html" CLASS="navR">&gt;&gt;</A>
</DIV>
</DIV>
<A HREF="002.html"><IMG SRC="001.jpg" CLASS="photo" STYLE="max-width:665px; max-height:1000px"></A>
</DIV>
<!-- %%BOTTOM_END%% -->
</DIV>
</DIV>
</DIV>
</DIV>
</BODY>
</HTML>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
<LINK REL="stylesheet" TYPE="text/css" HREF="dnalounge.css?3">
<TITLE>DNA Lounge: Anti-Nowhere League, 15 May 2014</TITLE>
<!-- %%HEAD_START%% -->
<STYLE TYPE="text/css">
<!--
.page { max-width: 100%; }
-->
</STYLE>
<!-- %%HEAD_END%% -->
<meta name="geo.position" content="37.771007;-122.412694">
<meta name="viewport" content="width=device-width, minimal-ui, minimum-scale=1.0, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
<meta property="og:type" content="article">
<meta property="fb:page_id" content="12161711085">
<meta property="fb:app_id" content="72575175403">
<meta name="twitter:creator" content="@dnalounge">
<meta name="twitter:site" content="@dnalounge">
<link rel="me" href="https://www.facebook.com/dnalounge">
<link rel="me" href="https://plus.google.com/118126466483537376327">
<link rel="publisher" href="https://plus.google.com/118126466483537376327">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<link rel="top" href="../../../">
<link rel="up" href="./#anti_nowhere_league">
<meta name="twitter:card" content="photo">
<meta name="description" content="DNA Lounge: Anti-Nowhere League, 15 May 2014">
<meta name="medium" content="image">
<meta property="og:image" content="http://www.dnalounge.com/gallery/2014/05-15/002.jpg">
<link rel="image_src" href="http://www.dnalounge.com/gallery/2014/05-15/002.jpg">
<link rel="first" href="001.html">
<link rel="prev" href="001.html">
<link rel="next" href="003.html">
<link rel="last" href="105.html">
<SCRIPT LANGUAGE="javascript" SRC="../../gallery.js?1"></SCRIPT>
<script src="hand.js"></script>
<script src="dragnav.js"></script>
</HEAD>
<BODY>
<DIV ID="outerContainer" STYLE="overflow-x: hidden;">
<DIV ID="previewContainer" STYLE="position: relative;">
<DIV ID="thisPage" CLASS="page">
<DIV CLASS="top">
<!-- %%MENU_START%% -->
<DIV CLASS="masthead"><A HREF="../../../"><IMG SRC="logo.gif"></A></DIV>
<UL CLASS="menu" ID="menu1"><LI>
<A HREF="../../../">Home</A>
</LI><LI><A HREF="../../../calendar/latest.html">Calendar</A>
</LI><LI><A HREF="../../../directions/">Directions</A>
</LI><LI><A HREF="../../../tickets/">Tickets</A>
</LI><LI><A HREF="../../../webcast/">Webcasts</A>
</LI><LI><A HREF="../../../contact/">Contact</A>
</LI></UL>
<!-- %%MENU_END%% -->
</DIV>
<DIV CLASS="bottom">
<!-- %%BOTTOM_START%% -->
<DIV ALIGN=CENTER>
<DIV CLASS="top">
<DIV CLASS="gwbox">
<A HREF="001.html" CLASS="navL">&lt;&lt;</A>
<A HREF="./#anti_nowhere_league">Anti-Nowhere League</A>
<A HREF="003.html" CLASS="navR">&gt;&gt;</A>
</DIV>
</DIV>
<A HREF="003.html"><IMG SRC="002.jpg" CLASS="photo" STYLE="max-width:1000px; max-height:665px"></A>
</DIV>
<!-- %%BOTTOM_END%% -->
</DIV>
</DIV>
</DIV>
</DIV>
</BODY>
</HTML>

This is a bit of code to solve jwz's scrolling problem ('javascript gallery swipe-right rocket surgery').

It works as follows:

  • There are two divs: an inner div, which will receive the page preview elements, and an outer div, with "overflow-x: hidden", to clip previews that are not visible. You have to do this or mobile safari tries to allow you to pan all over the viewport when you add the new element.

  • When you swipe left or right, it uses XMLHttpRequest to load the next or previous page into a newly-created HtmlDocument, then extracts the central page element and inserts it into the inner div.

  • When you've swiped far enough to generate a navigation, it replaces the contents of the document with the contents of the appropriately-loaded HtmlDocument and pushes the navigation state, for flicker-free navigation.

The biggest downside to this approach is that, to get clipping to work, the script sets an absolute value for the outer div's width the first time you try to swipe. This is not what you want on a desktop browser, where the window size can be changed.

/* Copyright © 2012-2014 Jamie Zawinski <jwz@dnalounge.com>
Permission to use, copy, modify, distribute, and sell this software
and its documentation for any purpose is hereby granted without
fee, provided that the above copyright notice appear in all copies
and that both that copyright notice and this permission notice
appear in supporting documentation. No representations are made
about the suitability of this software for any purpose. It is
provided "as is" without express or implied warranty.
Loading this code causes "drag left" and "drag right" gestures
to load the previous and next page, respectively, as per the
LINK REL="prev" and "next" tags in the document. If there's
no "prev"/"next", it uses "up" instead.
It also binds left-arrow and right-arrow.
*/
var ce = null;
var drag_start_x = null;
var drag_start_y = null;
var drag_current_x = null;
var drag_current_y = null;
var drag_debug = true;
function load_page(url, success, failure) {
// Loads the specified url into an HTML document; calls the success
// callback with the document. Returns the XMLHttpRequest object-- set its
// cancelled property to something truthy to cancel the request.
var doc = document.implementation.createHTMLDocument("fragment");
var req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.cancelled) { return; }
if (req.readyState === 4) {
if (req.status >= 200 && req.status < 300) {
doc.documentElement.innerHTML = req.responseText;
success(doc);
} else {
if (failure) {
failure(req);
}
}
req.onreadystatechange = function() { };
}
}.bind(this);
req.open("GET", url, true);
req.responseType = "";
req.send();
return req;
}
function style_outer_container(width) {
var oc = document.getElementById("outerContainer");
oc.style.width = width + "px";
}
// PagePreview objects manage the fragments of HTML necessary to show the
// previews of the next and previous pages.
function PagePreview(forward_p) {
this.forward_p = forward_p;
}
PagePreview.prototype.load = function() {
// Start loading the preview, if we haven't already.
if (!this.loading_p) {
this.loading_p = true;
this.url = drag_url(this.forward_p, false);
if (this.url) {
var width = document.documentElement.offsetWidth;
style_outer_container(width);
this.create_element(width);
// Load the page fragment from the URL.
this.request = load_page(this.url, function loadSuccess(document) {
this.document = document;
var otherElement = this.document.getElementById("thisPage");
if (otherElement) {
this.element.innerHTML = otherElement.innerHTML;
this.loaded_p = true;
}
}.bind(this));
ce.appendChild(this.element);
}
}
};
PagePreview.prototype.reset = function() {
// Clear out all the internal state that might be going on.
if (this.element) {
this.element.innerHTML = '';
}
if (this.req) {
// Make sure any completing outstanding XHR doesn't do any harm.
this.req.cancelled = true;
}
delete this.document;
delete this.url;
delete this.element;
delete this.loading_p;
delete this.loaded_p;
delete this.req;
};
PagePreview.prototype.create_element = function(width) {
this.element = document.createElement("div");
this.element.style.position = "absolute";
this.element.style.top = "0px";
this.element.style.width = width + "px";
this.element.style.left = (this.forward_p ? "+" : "-") + width + "px";
};
PagePreview.prev = new PagePreview(false);
PagePreview.next = new PagePreview(true);
function drag_zoomed_p() {
// If the page is pinch-zoomed in, don't do any of this stuff.
// But note that sometimes it gets zoomed in by 1px at random when
// dragging, so only consider it zoomed when it's a couple percent.
var r = window.innerWidth / document.documentElement.clientWidth;
return (r < 0.98);
}
function drag_start(e) { // finger down
drag_start_x = e.pageX;
drag_start_y = e.pageY;
drag_current_x = drag_start_x;
drag_current_y = drag_start_y;
if (drag_debug)
console.log ("drag_start " +
drag_start_x + " " + drag_start_y + ", " +
drag_current_x + " " + drag_current_y);
}
function drag_stop(e, good_p) { // finger up
var xoff = drag_current_x - drag_start_x;
var yoff = drag_current_y - drag_start_y;
ce.style.left = "0";
// ce.style.top = "0";
drag_start_x = null;
drag_start_y = null;
drag_current_x = null;
drag_current_y = null;
var xratio = Math.abs(xoff) / window.innerWidth;
var yratio = Math.abs(yoff) / window.innerWidth; // use width for both
if (drag_debug)
console.log ("drag_stop " + xoff + " " + yoff + ", " +
xratio + " " + yratio);
if (yratio > xratio) {
good_p = false;
if (drag_debug)
console.log ("drag_stop vertical");
} else if (xratio < 0.33) {
good_p = false;
if (drag_debug)
console.log ("drag_stop short horizontal");
}
if (good_p) {
drag_nav (xoff < 0);
}
}
function drag_url(forward_p, up_p) {
var next = null;
var up = null;
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === (forward_p ? "next" : "prev"))
next = link.href;
else if (link.rel === "up" && up_p)
up = link.href;
}
return next || up;
}
function drag_moved(e) { // finger moved
if (drag_zoomed_p()) {
if (drag_debug)
console.log ("drag_moved while zoomed, " +
window.innerWidth + ", " +
document.documentElement.clientWidth);
} else if (drag_start_x == null) {
if (drag_debug)
console.log ("drag_moved without start");
} else if (e.touches && (e.touches.length != 1)) {
// Multi-touch started: abort dragging.
if (drag_debug)
console.log ("drag_moved multi-touch " + e.touches.length);
drag_stop (e, false);
} else {
drag_current_x = e.pageX;
drag_current_y = e.pageY;
var xoff = drag_current_x - drag_start_x;
var yoff = drag_current_y - drag_start_y;
ce.style.left = xoff + "px";
// ce.style.top = yoff + "px";
if (drag_debug)
console.log("drag_moved " +
drag_current_x + " - " + drag_start_x + " = " + xoff + ", " +
drag_current_y + " - " + drag_start_y + " = " + yoff);
if (Math.abs(yoff) > window.innerHeight * 0.01 &&
Math.abs(yoff) > Math.abs(xoff) * 1.1) {
// We appear to be dragging more up/down than left/right.
// Just stop tracking immediately to end the flickery hell.
if (drag_debug)
console.log ("drag_moved vertical, abort");
drag_stop (e, false);
} else {
e.preventDefault();
var forward_p = xoff < 0;
var preview = forward_p ? PagePreview.next : PagePreview.prev;
preview.load();
}
}
}
function drag_nav(forward_p) { // load "next" or "up" link.
var preview = forward_p ? PagePreview.next : PagePreview.prev;
if (preview.loaded_p) {
// Remember what the new contents are supposed to be...
var newContents = preview.document.documentElement.innerHTML;
var newUrl = preview.url;
// Reset the preview state so the new page can do it all over again.
// NOTE: If we were clever we would remember this page as the preview in
// the opposite direction. We are not clever yet.
PagePreview.next.reset();
PagePreview.prev.reset();
// Blow away our contents and update the URL-- POOF ITS LIKE NAVIGATION.
document.documentElement.innerHTML = newContents;
window.history.pushState({}, '', newUrl);
// But now we need to re-initialize drag navigation for the new contents.
init_drag_nav();
} else {
// Something went wrong loading the preview; just navigate like normal.
var target = drag_url(forward_p, true);
if (target) {
if (drag_debug)
console.log ("drag_nav " + target);
document.location = target;
} else if (drag_debug) {
console.log ("drag_nav: no links");
}
}
}
function arrow_keys(e) {
e = e || window.event;
if (e.keyCode == 37) { drag_nav (false); } // left-arrow
else if (e.keyCode == 39) { drag_nav (true); } // right-arrow
// up-arrow = 38, down-arrow = 40.
}
function init_drag_nav() {
ce = document.getElementById("previewContainer");
if (!ce) {
if (drag_debug)
console.log("drag_nav can't find #previewContainer");
return;
}
var n = document.body;
if (drag_debug)
console.log ("drag_nav installed");
n.addEventListener('touchstart', drag_start, false);
n.addEventListener('touchmove', drag_moved, false);
n.addEventListener('touchend', function(e) { drag_stop (e, true); },
false);
n.addEventListener('touchcancel',function(e) { drag_stop (e, false); },
false);
document.onkeyup = arrow_keys;
// HAX: Let me work the problem without having touch events
n.addEventListener('pointerdown', drag_start, false);
n.addEventListener('pointermove', function(e) { if(drag_start_x) { drag_moved(e); } },
false);
n.addEventListener('pointerup', function(e) { drag_stop(e, true); },
false);
n.addEventListener('pointercancel', function(e) { drag_stop(e, false); },
false);
}
// When this file is loaded, there is no BODY yet.
// Use the not-supported-in-old-browsers DOM event to wait.
//
// I used to do this portably by firing a timer every 1/10th second
// until BODY existed, but that stopped working for some unknown
// reason.
//
document.addEventListener("DOMContentLoaded", init_drag_nav);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment