Permalink
Browse files

Merge pull request #66 from joseph/transitions

Smoother transitions and animation in more browsers. Fixes #2.
  • Loading branch information...
joseph committed Oct 17, 2011
2 parents 0ecfbe9 + b62ab96 commit d905119499ab40f7245f0bd6a58bf92b6f8eb751
Showing with 241 additions and 268 deletions.
  1. +22 −6 src/compat/env.js
  2. +11 −13 src/core/component.js
  3. +21 −0 src/core/events.js
  4. +6 −12 src/core/reader.js
  5. +15 −0 src/core/styles.js
  6. +26 −8 src/dimensions/columns.js
  7. +26 −20 src/flippers/scroller.js
  8. +95 −191 src/flippers/slider.js
  9. +15 −5 styles/monocore.css
  10. +4 −13 test/flippers/index.html
View
@@ -214,6 +214,11 @@ Monocle.Env = function () {
// TEST FOR OPTIONAL CAPABILITIES
+ // Does it do CSS transitions?
+ ["supportsTransition", function () {
+ result(css.supportsPropertyWithAnyPrefix('transition'))
+ }],
+
// Can we find nodes in a document with an XPath?
//
["supportsXPath", testForFunction("document.evaluate")],
@@ -275,10 +280,14 @@ Monocle.Env = function () {
// Webkit-based browsers put floated elements in the wrong spot when
// columns are used -- they appear way down where they would be if there
// were no columns. Presumably the float positions are calculated before
- // the columns. A bug has been lodged.
+ // the columns. A bug has been lodged, and it's fixed in recent WebKits.
//
- // FIXME: Detection not yet implemented.
- ["floatsIgnoreColumns", testNotYetImplemented(false)],
+ ["floatsIgnoreColumns", function () {
+ if (!Monocle.Browser.is.WebKit) { return result(false); }
+ match = navigator.userAgent.match(/AppleWebKit\/([\d\.]+)/);
+ if (!match) { return result(false); }
+ return result(match[1] < "534.30");
+ }],
// The latest engines all agree that if a body is translated leftwards,
// its scrollWidth is shortened. But some older WebKits (notably iOS4)
@@ -311,8 +320,6 @@ Monocle.Env = function () {
// In iOS, the frame is clipped by overflow:hidden, so this doesn't seem to
// be a problem.
//
- // TODO: Is there a way to detect this?
- //
["relativeIframeExpands", function () {
result(navigator.userAgent.indexOf("Android 2") >= 0);
}],
@@ -333,7 +340,6 @@ Monocle.Env = function () {
// min-width is set, it's more difficult to recognise 1 page components,
// so we generally don't want to force it unless we have to.
//
- // FIXME: This is not detecting correctly for iOS3.
["forceColumns", function () {
loadTestFrame(function (fr) {
var bd = fr.contentDocument.body;
@@ -387,6 +393,16 @@ Monocle.Env = function () {
loadTestFrame(function (fr) {
result(fr.parentNode.scrollWidth > testFrameSize);
});
+ }],
+
+ // For some reason, iOS MobileSafari sometimes loses track of a page after
+ // slideOut -- it thinks it has an x-translation of 0, rather than -768 or
+ // whatever. So the page gets "stuck" there, until it is given a non-zero
+ // x-translation. The workaround is to set a non-zero duration on the jumpIn,
+ // which seems to force WebKit to recalculate the x of the page. Weird, yeah.
+ //
+ ["stickySlideOut", function () {
+ result(Monocle.Browser.is.MobileSafari);
}]
];
View
@@ -38,24 +38,22 @@ Monocle.Component = function (book, id, index, chapters, source) {
// Makes this component the active component for the pageDiv. There are
// several strategies for this (see loadFrame).
//
- // Some strategies are time-consuming (and usually asynchronous), some are
- // not. When the component has been loaded into the pageDiv's frame, the
- // callback will be invoked with the pageDiv and this component as arguments.
+ // When the component has been loaded into the pageDiv's frame, the callback
+ // will be invoked with the pageDiv and this component as arguments.
//
function applyTo(pageDiv, callback) {
var evtData = { 'page': pageDiv, 'source': p.source };
pageDiv.m.reader.dispatchEvent('monocle:componentchanging', evtData);
- return loadFrame(
- pageDiv,
- function () {
- setupFrame(
- pageDiv,
- pageDiv.m.activeFrame,
- function () { callback(pageDiv, API) }
- );
- }
- );
+ var onLoaded = function () {
+ setupFrame(
+ pageDiv,
+ pageDiv.m.activeFrame,
+ function () { callback(pageDiv, API) }
+ );
+ }
+
+ Monocle.defer(function () { loadFrame(pageDiv, onLoaded); });
}
View
@@ -277,6 +277,27 @@ Monocle.Events.listenForTap = function (elem, fn, activeClass) {
Monocle.Events.deafenForTap = Monocle.Events.deafenForContact;
+// Listen for the next transition-end event on the given element, call
+// the function, then deafen.
+//
+// Returns a function that can be used to cancel the listen early.
+//
+Monocle.Events.afterTransition = function (elem, fn) {
+ var evtName = "transitionend";
+ if (Monocle.Browser.is.WebKit) {
+ evtName = 'webkitTransitionEnd';
+ } else if (Monocle.Browser.is.Opera) {
+ evtName = 'oTransitionEnd';
+ }
+ var l = null, cancel = null;
+ l = function () { fn(); cancel(); }
+ cancel = function () { Monocle.Events.deafen(elem, evtName, l); }
+ Monocle.Events.listen(elem, evtName, l);
+ return cancel;
+}
+
+
+
// BROWSERHACK: iOS touch events on iframes are busted. The TouchMonitor,
// transposes touch events on underlying iframes onto the elements that
// sit above them. It's a massive hack.
View
@@ -191,7 +191,7 @@ Monocle.Reader = function (node, bookData, options, onLoadCallback) {
function clampStylesheets(customStylesheet) {
var defCSS = k.DEFAULT_STYLE_RULES;
if (Monocle.Browser.env.floatsIgnoreColumns) {
- defCSS += "html#RS\\:monocle * { float: none !important; }";
+ defCSS.push("html#RS\\:monocle * { float: none !important; }");
}
p.defaultStyles = addPageStyles(defCSS, false);
if (customStylesheet) {
@@ -306,7 +306,7 @@ Monocle.Reader = function (node, bookData, options, onLoadCallback) {
recalculateDimensions(true);
dispatchEvent("monocle:resize");
},
- k.durations.RESIZE_DELAY
+ k.RESIZE_DELAY
);
}
@@ -746,13 +746,7 @@ Monocle.Reader = function (node, bookData, options, onLoadCallback) {
return API;
}
-Monocle.Reader.durations = {
- RESIZE_DELAY: 100
-}
-Monocle.Reader.abortMessage = {
- CLASSNAME: "monocleAbortMessage",
- TEXT: "Your browser does not support this technology."
-}
+Monocle.Reader.RESIZE_DELAY = 100;
Monocle.Reader.DEFAULT_SYSTEM_ID = 'RS:monocle'
Monocle.Reader.DEFAULT_CLASS_PREFIX = 'monelem_'
Monocle.Reader.DEFAULT_STYLE_RULES = [
@@ -763,9 +757,9 @@ Monocle.Reader.DEFAULT_STYLE_RULES = [
"overflow: visible !important;" +
"}",
"html#RS\\:monocle body {" +
- "margin: 0;"+
- "border: none;"+
- "padding: 0;"+
+ "margin: 0 !important;"+
+ "border: none !important;"+
+ "padding: 0 !important;"+
"-webkit-text-size-adjust: none;" +
"}",
"html#RS\\:monocle body * {" +
View
@@ -57,6 +57,21 @@ Monocle.Styles = {
}
s.MozTransform = s.OTransform = s.transform = "translateY("+y+")";
return y;
+ },
+
+
+ transitionFor: function (elem, prop, duration, timing, delay) {
+ var tProps = Monocle.Browser.css.toDOMProps('transition');
+ var pProps = Monocle.Browser.css.toCSSProps(prop);
+ timing = timing || "linear";
+ delay = (delay || 0)+"ms";
+ for (var i = 0, ii = tProps.length; i < ii; ++i) {
+ var t = "none";
+ if (duration) {
+ t = [pProps[i], duration+"ms", timing, delay].join(" ");
+ }
+ elem.style[tProps[i]] = t;
+ }
}
}
View
@@ -9,6 +9,9 @@ Monocle.Dimensions.Columns = function (pageDiv) {
width: 0
}
+ // Logically, forceColumn browsers can't have a gap, because that would
+ // make the minWidth > 200%. But how much greater? Not worth the effort.
+ k.GAP = Monocle.Browser.env.forceColumns ? 0 : 20;
function update(callback) {
setColumnWidth();
@@ -32,9 +35,9 @@ Monocle.Dimensions.Columns = function (pageDiv) {
p.width = pdims.width;
var rules = Monocle.Styles.rulesToString(k.STYLE["columned"]);
- rules += Monocle.Browser.css.toCSSDeclaration('column-width', p.width+'px');
- rules += Monocle.Browser.css.toCSSDeclaration('column-gap', 0);
- rules += Monocle.Browser.css.toCSSDeclaration('transform', "translateX(0)");
+ rules += Monocle.Browser.css.toCSSDeclaration('column-width', pdims.col+'px');
+ rules += Monocle.Browser.css.toCSSDeclaration('column-gap', k.GAP+'px');
+ rules += Monocle.Browser.css.toCSSDeclaration('transform', 'translateX(0)');
if (Monocle.Browser.env.forceColumns && ce.scrollHeight > pdims.height) {
rules += Monocle.Styles.rulesToString(k.STYLE['column-force']);
@@ -74,6 +77,9 @@ Monocle.Dimensions.Columns = function (pageDiv) {
var w = Math.max(bd.scrollWidth, de.scrollWidth);
+ // Add one because the final column doesn't have right gutter.
+ w += k.GAP;
+
if (!Monocle.Browser.env.widthsIgnoreTranslate && p.page.m.offset) {
w += p.page.m.offset;
}
@@ -83,7 +89,11 @@ Monocle.Dimensions.Columns = function (pageDiv) {
function pageDimensions() {
var elem = p.page.m.sheafDiv;
- return { width: elem.clientWidth, height: elem.clientHeight }
+ return {
+ col: elem.clientWidth,
+ width: elem.clientWidth + k.GAP,
+ height: elem.clientHeight
+ }
}
@@ -97,16 +107,25 @@ Monocle.Dimensions.Columns = function (pageDiv) {
}
- function translateToLocus(locus) {
+ // Moves the columned element to the offset implied by the locus.
+ //
+ // The 'transition' argument is optional, allowing the translation to be
+ // animated. If not given, no change is made to the columned element's
+ // transition property.
+ //
+ function translateToLocus(locus, transition) {
var offset = locusToOffset(locus);
p.page.m.offset = offset;
- translateToOffset(offset);
+ translateToOffset(offset, transition);
return offset;
}
- function translateToOffset(offset) {
+ function translateToOffset(offset, transition) {
var ce = columnedElement();
+ if (transition) {
+ Monocle.Styles.affix(ce, "transition", transition);
+ }
Monocle.Styles.affix(ce, "transform", "translateX(-"+offset+"px)");
}
@@ -188,5 +207,4 @@ Monocle.Dimensions.Columns.STYLE = {
}
}
-
Monocle.pieceLoaded("dimensions/columns");
View
@@ -4,7 +4,7 @@ Monocle.Flippers.Scroller = function (reader, setPageFn) {
var k = API.constants = API.constructor;
var p = API.properties = {
pageCount: 1,
- duration: 200
+ duration: 300
}
@@ -58,23 +58,24 @@ Monocle.Flippers.Scroller = function (reader, setPageFn) {
function frameToLocus(locus) {
+ if (locus.boundarystart || locus.boundaryend) { return; }
p.turning = true;
-
- var x = page().m.dimensions.locusToOffset(locus);
- var bdy = page().m.activeFrame.contentDocument.body;
- if (false && typeof WebKitTransitionEvent != "undefined") {
- bdy.style.webkitTransition = "-webkit-transform " +
- p.duration + "ms ease-out 0ms";
- bdy.style.webkitTransform = "translateX(-"+x+"px)";
- Monocle.Events.listen(
- bdy,
- 'webkitTransitionEnd',
- function () {
- p.turning = false;
- p.reader.dispatchEvent('monocle:turn');
- }
- );
+ var dims = page().m.dimensions;
+ var fr = page().m.activeFrame;
+ var bdy = fr.contentDocument.body;
+ var anim = true;
+ if (p.activeComponent != fr.m.component) {
+ // No animation.
+ p.activeComponent = fr.m.component;
+ dims.translateToLocus(locus, "none");
+ Monocle.defer(turned);
+ } else if (Monocle.Browser.env.supportsTransition) {
+ // Native animation.
+ dims.translateToLocus(locus, p.duration+"ms ease-in 0ms");
+ Monocle.Events.afterTransition(bdy, turned);
} else {
+ // Old-school JS animation.
+ var x = dims.locusToOffset(locus);
var finalX = 0 - x;
var stamp = (new Date()).getTime();
var frameRate = 40;
@@ -86,21 +87,26 @@ Monocle.Flippers.Scroller = function (reader, setPageFn) {
(new Date()).getTime() - stamp > p.duration ||
Math.abs(currX - finalX) <= Math.abs((currX + step) - finalX)
) {
- clearTimeout(bdy.animInterval)
Monocle.Styles.setX(bdy, finalX);
- p.turning = false;
- p.reader.dispatchEvent('monocle:turn');
+ turned();
} else {
Monocle.Styles.setX(bdy, destX);
currX = destX;
+ setTimeout(stepFn, frameRate);
}
p.currX = destX;
}
- bdy.animInterval = setInterval(stepFn, frameRate);
+ stepFn();
}
}
+ function turned() {
+ p.turning = false;
+ p.reader.dispatchEvent('monocle:turn');
+ }
+
+
// THIS IS THE CORE API THAT ALL FLIPPERS MUST PROVIDE.
API.pageCount = p.pageCount;
API.addPage = addPage;
Oops, something went wrong.

0 comments on commit d905119

Please sign in to comment.