You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
601 lines
18 KiB
601 lines
18 KiB
define([ |
|
"dojo/_base/kernel", |
|
"dojo/_base/array", |
|
"dojo/_base/connect", |
|
"dojo/_base/event", |
|
"dojo/dom-class", |
|
"dojo/dom-construct", |
|
"dojo/dom-style", |
|
"./_css3" |
|
], function(kernel, array, connect, event, domClass, domConstruct, domStyle, css3){ |
|
// module: |
|
// dojox/mobile/pageTurningUtils |
|
|
|
kernel.experimental("dojox.mobile.pageTurningUtils"); |
|
|
|
return function(){ |
|
// summary: |
|
// Utilities to provide page turning effects just like turning a real book. |
|
// example: |
|
// | require([ |
|
// | "dojo/ready", |
|
// | "dojox/mobile/pageTurningUtils" |
|
// | ], function(ready, pageTurningUtils){ |
|
// | var utils = new pageTurningUtils(); |
|
// | ready(function(){ |
|
// | utils.init(300, 400); // Specify width and height by pixels |
|
// | utils.initCatalog(document.getElementById("catalog")); |
|
// | }); |
|
// | ); |
|
// | <div id="catalog"> |
|
// | <div id="page1"> |
|
// | <div id="front1"><img src="img1.png"></div> |
|
// | <div id="back1"><img src="img2.png"></div> |
|
// | </div> |
|
// | <div id="page2"> |
|
// | <div id="front2"><img src="img3.png"></div> |
|
// | <div id="back2"><img src="img4.png"></div> |
|
// | </div> |
|
// | <div id="page3"> |
|
// | <div id="front3"><img src="img5.png"></div> |
|
// | <div id="back3"></div> |
|
// | </div> |
|
// | </div> |
|
|
|
this.w = 0; |
|
this.h = 0; |
|
this.turnfrom = "top"; |
|
this.page = 1; |
|
this.dogear = 1.0; |
|
this.duration = 2; |
|
this.alwaysDogeared = false; |
|
|
|
/* Internal properties */ |
|
this._styleParams = {}; |
|
this._catalogNode = null; |
|
this._currentPageNode = null; |
|
this._transitionEndHandle = null; |
|
|
|
this.init = function(/*int*/w, /*int*/h, /*String?*/turnfrom, |
|
/*int?*/page, /*Number?*/dogear, /*Number?*/duration, |
|
/*Boolean?*/alwaysDogeared){ |
|
// summary: |
|
// Sets property values necessary for calculating the style parameters |
|
// for page positioning and page turning effects. |
|
// w: int |
|
// The width of each page by pixels. You cannot specify it by percentage. |
|
// h: int |
|
// The height of each page by pixels. You cannot specify it by percentage. |
|
// turnfrom: String? |
|
// Specifies from which side/corner the page turning starts. |
|
// You can choose from "top", "bottom" or "left". Defaults to "top". |
|
// If "top", each page is turned from top-right corner of the page. |
|
// If "bottom", each page is turned from bottom-right corner of the page. |
|
// And if "left", each page is turned from top-left corner of the page. |
|
// The page is shown as dog-eared except the case of "bottom". |
|
// page: int? |
|
// The number of pages shown in the screen at a time. |
|
// This parameter should be either of 1 or 2. Defaults to 1. |
|
// If 1, the only one side of two facing pages are shown. |
|
// If 2, the two facing pages are shown at a time. |
|
// dogear: Number? |
|
// The ratio of actual dog-ear width to the maximum dog-ear width which is |
|
// 11 percent of the page width (= 0.11 * w). |
|
// This parameter should be a float number between 0 and 1. Defaults to 1. |
|
// The actual dog-ear width is calculated by the following formula: |
|
// | 0.11 * w * dogear. |
|
// This parameter is ignored if "bottom" is specified to turnfrom parameter. |
|
// duration: Number? |
|
// The duration of page turning animations by seconds. (ex. 1.5, 3, etc) |
|
// Defaults to 2. |
|
// alwaysDogeared: Boolean? |
|
// Specifies whether all pages are always dog-eared or not. |
|
// If true, all pages are always dog-eared. |
|
// If false, only the current page is dog-eared while the others are not. |
|
// This parameter is ignored if "bottom" is specified to turnfrom parameter. |
|
|
|
// Set property values |
|
this.w = w; |
|
this.h = h; |
|
this.turnfrom = turnfrom ? turnfrom : this.turnfrom; |
|
this.page = page ? page : this.page; |
|
this.dogear = typeof dogear !== 'undefined' ? dogear : this.dogear; |
|
this.duration = typeof duration !== 'undefined' ? duration : this.duration; |
|
this.alwaysDogeared = typeof alwaysDogeared !== 'undefined' ? alwaysDogeared : this.alwaysDogeared; |
|
|
|
if(this.turnfrom === "bottom"){ // dog-ear is not supported if using "bottom" |
|
this.alwaysDogeared = true; |
|
} |
|
// Calculate style parameters for page positioning and page turning effects |
|
this._calcStyleParams(); |
|
}; |
|
|
|
this._calcStyleParams = function(){ |
|
// tags: |
|
// private |
|
var tan58 = Math.tan(58 * Math.PI/180), |
|
cos32 = Math.cos(32 * Math.PI/180), |
|
sin32 = Math.sin(32 * Math.PI/180), |
|
tan32 = Math.tan(32 * Math.PI/180), |
|
w = this.w, |
|
h = this.h, |
|
page = this.page, |
|
turnfrom = this.turnfrom, |
|
params = this._styleParams; |
|
|
|
// Calculate each div size and position based on the page turning algorithm |
|
// fw: frontWidth, fh: frontHeight, dw: dogear, cx: posX, cy: posY, |
|
// dx: dogearX, dy: dogearY, fy:actualPagePos |
|
var fold = w * tan58, |
|
Q = fold, |
|
fw = Q * sin32 + Q * cos32 * tan58, |
|
fh = fold + w + w/tan58, |
|
dw = w * 0.11 * this.dogear, |
|
pw = w - dw, |
|
base = pw * cos32, |
|
cx, cy, dx, dy, fy; // These params depend on the types of "turnfrom" parameter |
|
|
|
switch(this.turnfrom){ // TODO Should separate each case as a plugin? |
|
case "top": |
|
cx = fw - base; |
|
cy = base * tan58; |
|
dx = fw - dw; |
|
dy = cy + pw/tan58 - 7; |
|
fy = cy/cos32; |
|
|
|
params.init = { |
|
page: css3.add({ |
|
top: -fy + "px", |
|
left: (-fw + (page === 2 ? w : 0)) + "px", |
|
width: fw + "px", |
|
height: fh + "px" |
|
}, { |
|
transformOrigin: "100% 0%" |
|
}), |
|
front: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
back: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
shadow: { |
|
display: "", |
|
left: fw + "px", |
|
height: h * 1.5 + "px" |
|
} |
|
}; |
|
params.turnForward = { |
|
page: css3.add({}, { |
|
transform: "rotate(0deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ fw + "px," + fy +"px) rotate(0deg)", |
|
transformOrigin: "-110px -18px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate("+ (fw - w) + "px," + fy +"px) rotate(0deg)", |
|
transformOrigin: "0px 0px" |
|
}) |
|
}; |
|
params.turnBackward = { |
|
page: css3.add({}, { |
|
transform: "rotate(-32deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ cx + "px," + cy +"px) rotate(32deg)", |
|
transformOrigin: "0px 0px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate("+ dx + "px," + dy +"px) rotate(-32deg)", |
|
transformOrigin: "0px 0px" |
|
}) |
|
}; |
|
break; |
|
|
|
case "bottom": |
|
cx = fw - (h * sin32 + w * cos32) - 2; |
|
cy = fh - (h + w/tan32) * cos32; |
|
dx = fw; |
|
dy = fh - w/sin32 - h; |
|
fy = fh - w/tan32 - h; |
|
|
|
params.init = { |
|
page: css3.add({ |
|
top: (-fy + 50) + "px", |
|
left: (-fw + (page === 2 ? w : 0)) + "px", |
|
width: fw + "px", |
|
height: fh + "px" |
|
}, { |
|
transformOrigin: "100% 100%" |
|
}), |
|
front: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
back: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
shadow: { |
|
display: "none" |
|
} |
|
}; |
|
params.turnForward = { |
|
page: css3.add({}, { |
|
transform: "rotate(0deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ fw + "px," + fy +"px) rotate(0deg)", |
|
transformOrigin: "-220px 35px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate("+ (w * 2) + "px," + fy +"px) rotate(0deg)", |
|
transformOrigin: "0px 0px" |
|
}) |
|
}; |
|
params.turnBackward = { |
|
page: css3.add({}, { |
|
transform: "rotate(32deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ cx + "px," + cy +"px) rotate(-32deg)", |
|
transformOrigin: "0px 0px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate("+ dx + "px," + dy +"px) rotate(0deg)", |
|
transformOrigin: "0px 0px" |
|
}) |
|
}; |
|
break; |
|
|
|
case "left": |
|
cx = -w; |
|
cy = pw/tan32 - 2; |
|
dx = -pw; |
|
dy = fy = pw/sin32 + dw * sin32; |
|
|
|
params.init = { |
|
page: css3.add({ |
|
top: -cy + "px", |
|
left: w + "px", |
|
width: fw + "px", |
|
height: fh + "px" |
|
}, { |
|
transformOrigin: "0% 0%" |
|
}), |
|
front: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
back: css3.add({ |
|
width: w + "px", |
|
height: h + "px" |
|
}, { |
|
boxShadow: "0 0" |
|
}), |
|
shadow: { |
|
display: "", |
|
left: "-4px", |
|
height: ((page === 2 ? h * 1.5 : h) + 50) + "px" |
|
} |
|
}; |
|
params.turnForward = { |
|
page: css3.add({}, { |
|
transform: "rotate(0deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ cx + "px," + cy +"px) rotate(0deg)", |
|
transformOrigin: "160px 68px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate(0px," + cy +"px) rotate(0deg)", |
|
transformOrigin: "0px 0px" |
|
}) |
|
}; |
|
params.turnBackward = { |
|
page: css3.add({}, { |
|
transform: "rotate(32deg)" |
|
}), |
|
front: css3.add({}, { |
|
transform: "translate("+ (-dw) + "px," + dy +"px) rotate(-32deg)", |
|
transformOrigin: "0px 0px" |
|
}), |
|
back: css3.add({}, { |
|
transform: "translate("+ dx + "px," + dy +"px) rotate(32deg)", |
|
transformOrigin: "top right" |
|
}) |
|
}; |
|
break; |
|
} |
|
|
|
params.init.catalog = { // catalogNode |
|
width: (page === 2 ? w * 2 : w) + "px", |
|
height: ((page === 2 ? h * 1.5 : h) + (turnfrom == "top" ? 0 : 50)) + "px" |
|
}; |
|
}; |
|
|
|
this.getChildren = function(/*DomNode*/node){ |
|
return array.filter(node.childNodes, function(n){ return n.nodeType === 1; }); |
|
}; |
|
|
|
this.getPages = function(){ |
|
// summary: |
|
// Get the array of all page nodes. |
|
return this._catalogNode ? this.getChildren(this._catalogNode) : null; |
|
}; |
|
|
|
this.getCurrentPage = function(){ |
|
// summary: |
|
// Get the current page node. |
|
return this._currentPageNode; |
|
}; |
|
|
|
this.getIndexOfPage = function(/*DomNode*/pageNode, /*DomNode[]?*/pages){ |
|
if(!pages){ |
|
pages = this.getPages(); |
|
} |
|
for(var i=0; i<pages.length; i++){ |
|
if(pageNode === pages[i]){ return i; } |
|
} |
|
return -1; |
|
}; |
|
|
|
this.getNextPage = function(/*DomNode*/pageNode){ |
|
for(var n = pageNode.nextSibling; n; n = n.nextSibling){ |
|
if(n.nodeType === 1){ return n; } |
|
} |
|
return null; |
|
}; |
|
|
|
this.getPreviousPage = function(/*DomNode*/pageNode){ |
|
for(var n = pageNode.previousSibling; n; n = n.previousSibling){ |
|
if(n.nodeType === 1){ return n; } |
|
} |
|
return null; |
|
}; |
|
|
|
this.isPageTurned = function(/*DomNode*/pageNode){ |
|
return pageNode.style[css3.name("transform")] == "rotate(0deg)"; |
|
}; |
|
|
|
this._onPageTurned = function(e){ |
|
event.stop(e); |
|
if(domClass.contains(e.target, "mblPageTurningPage")){ |
|
this.onPageTurned(e.target); |
|
} |
|
}; |
|
|
|
this.onPageTurned = function(/*DomNode*/ /*===== pageNode =====*/){ |
|
// summary: |
|
// Stub function to which your application connects |
|
// to handle the event when each page is tured |
|
// description: |
|
// Called just after each page is turned. |
|
}; |
|
|
|
this.initCatalog = function(/*DomNode*/catalogNode){ |
|
// summary: |
|
// Initializes the specified catalog/book. |
|
// description: |
|
// Apply style class and other various styles to the specified catalog node |
|
// for initialization. |
|
// This function must be called every time when property values |
|
// are changed by calling this.init(...) function. |
|
// catalogNode: DOMNode |
|
// The catalog node to be initialized. |
|
|
|
if(this._catalogNode != catalogNode){ |
|
if(this._transitionEndHandle){ |
|
connect.disconnect(this._transitionEndHandle); |
|
} |
|
this._transitionEndHandle = connect.connect(catalogNode, css3.name("transitionEnd"), this, "_onPageTurned"); |
|
this._catalogNode = catalogNode; |
|
} |
|
|
|
// Initialize catalog node |
|
domClass.add(catalogNode, "mblPageTurningCatalog"); |
|
domStyle.set(catalogNode, this._styleParams.init.catalog); |
|
|
|
// Initialize child pages |
|
var pages = this.getPages(); |
|
array.forEach(pages, function(pageNode){ |
|
this.initPage(pageNode); |
|
}, this); |
|
|
|
this.resetCatalog(); |
|
}; |
|
|
|
this._getBaseZIndex = function(){ |
|
// Check z-index of catalogNode |
|
return this._catalogNode.style.zIndex || 0; |
|
}; |
|
|
|
this.resetCatalog = function(){ |
|
// summary: |
|
// Resets the catalog by adjust the state of child pages. |
|
// description: |
|
// Adjust z-index and dog-ear show/hide state of each child page. |
|
// This function must be called when you add a new page |
|
// or remove some page after initialization. |
|
|
|
// Adjust z-index and dog-ear of child pages |
|
var pages = this.getPages(), |
|
len = pages.length, |
|
base = this._getBaseZIndex(); |
|
for(var i = len - 1; i>=0; i--){ |
|
var pageNode = pages[i]; |
|
this.showDogear(pageNode); |
|
if(this.isPageTurned(pageNode)){ |
|
pageNode.style.zIndex = base + len + 1; |
|
}else{ |
|
pageNode.style.zIndex = base + len - i; |
|
!this.alwaysDogeared && this.hideDogear(pageNode); |
|
this._currentPageNode = pageNode; |
|
} |
|
} |
|
if(!this.alwaysDogeared && this._currentPageNode != pages[len - 1]){ |
|
this.showDogear(this._currentPageNode); |
|
} |
|
}; |
|
|
|
this.initPage = function(/*DomNode*/pageNode, /*int?*/dir){ |
|
// summary: |
|
// Initializes the specified page. |
|
// description: |
|
// Apply style class and other various styles to the specified page node |
|
// for initialization. |
|
// This function must be called when you add a new page after initialization. |
|
// pageNode: DOMNode |
|
// The page node to be initialized. |
|
// dir: int? |
|
// Specified whether the page is turned or not at initialization. |
|
// This parameter should be either of 1 or -1. |
|
// If 1, the page is turned at initialization. If -1, it is not turned. |
|
|
|
// Create shadow node if not exist |
|
var childNodes = this.getChildren(pageNode); |
|
while(childNodes.length < 3){ |
|
pageNode.appendChild(domConstruct.create("div", null)); |
|
childNodes = this.getChildren(pageNode); |
|
} |
|
|
|
// Check if it is the first time to initialize this page node or not |
|
var isFirst = !domClass.contains(pageNode, "mblPageTurningPage"); |
|
|
|
// Apply style class |
|
domClass.add(pageNode, "mblPageTurningPage"); |
|
domClass.add(childNodes[0], "mblPageTurningFront"); // frontNode |
|
domClass.add(childNodes[1], "mblPageTurningBack"); // backNode |
|
domClass.add(childNodes[2], "mblPageTurningShadow"); // shadowNode |
|
|
|
// Apply styles |
|
var p = this._styleParams.init; |
|
domStyle.set(pageNode, p.page); |
|
domStyle.set(childNodes[0], p.front); // frontNode |
|
domStyle.set(childNodes[1], p.back); // backNode |
|
p.shadow && domStyle.set(childNodes[2], p.shadow); // shadowNode |
|
|
|
if(!dir){ |
|
// Determine whether to turn this page or not |
|
if(isFirst && this._currentPageNode){ |
|
var pages = this.getPages(); |
|
dir = this.getIndexOfPage(pageNode) < this.getIndexOfPage(this._currentPageNode) ? 1 : -1; |
|
}else{ |
|
dir = this.isPageTurned(pageNode) ? 1 : -1; |
|
} |
|
} |
|
this._turnPage(pageNode, dir, 0); |
|
}; |
|
|
|
this.turnToNext = function(/*Number?*/duration){ |
|
// summary: |
|
// Turns a page forward. |
|
// description: |
|
// The current page is turned forward if it is not the last page of the book. |
|
// duration: Number? |
|
// The duration of page turning animations by seconds. (ex. 1.5, 3, etc) |
|
// If this parameter is omitted, this.duration property value is used. |
|
var nextPage = this.getNextPage(this._currentPageNode); |
|
if(nextPage){ |
|
this._turnPage(this._currentPageNode, 1, duration); |
|
this._currentPageNode = nextPage; |
|
} |
|
}; |
|
|
|
this.turnToPrev = function(/*Number?*/duration){ |
|
// summary: |
|
// Turns a page backward. |
|
// description: |
|
// The current page is turned backward if it is not the first page of the book. |
|
// duration: Number? |
|
// The duration of page turning animations by seconds. (ex. 1.5, 3, etc) |
|
// If this parameter is omitted, this.duration property value is used. |
|
var prevPage = this.getPreviousPage(this._currentPageNode); |
|
if(prevPage){ |
|
this._turnPage(prevPage, -1, duration); |
|
this._currentPageNode = prevPage; |
|
} |
|
}; |
|
|
|
this.goTo = function(/*int*/index){ |
|
// summary: |
|
// Jumps to the specified page without page turning animations. |
|
// index: int |
|
// The index of the page you want to jump to. |
|
var pages = this.getPages(); |
|
if(this._currentPageNode === pages[index] || pages.length <= index) { return; } |
|
|
|
var goBackward = index < this.getIndexOfPage(this._currentPageNode, pages); |
|
while(this._currentPageNode !== pages[index]) { |
|
goBackward ? this.turnToPrev(0) : this.turnToNext(0); |
|
} |
|
}; |
|
|
|
this._turnPage = function(/*DomNode*/pageNode, /*int*/dir, /*Number?*/duration){ |
|
// tags: |
|
// private |
|
var childNodes = this.getChildren(pageNode), |
|
d = ((typeof duration !== 'undefined') ? duration : this.duration) + "s", |
|
p = (dir === 1) ? this._styleParams.turnForward : this._styleParams.turnBackward; |
|
|
|
// Apply styles for page turning animations |
|
p.page[css3.name("transitionDuration")] = d; |
|
domStyle.set(pageNode, p.page); |
|
|
|
p.front[css3.name("transitionDuration")] = d; |
|
domStyle.set(childNodes[0], p.front); // frontNode |
|
|
|
p.back[css3.name("transitionDuration")] = d; |
|
domStyle.set(childNodes[1], p.back); // backNode |
|
|
|
// Adjust z-index and dog-ear |
|
var pages = this.getPages(), |
|
nextPage = this.getNextPage(pageNode), |
|
len = pages.length, |
|
base = this._getBaseZIndex(); |
|
if(dir === 1){ |
|
pageNode.style.zIndex = base + len + 1; |
|
if(!this.alwaysDogeared && nextPage && this.getNextPage(nextPage)){ |
|
this.showDogear(nextPage); |
|
} |
|
}else{ |
|
if(nextPage){ |
|
nextPage.style.zIndex = base + len - this.getIndexOfPage(nextPage, pages); |
|
!this.alwaysDogeared && this.hideDogear(nextPage); |
|
} |
|
} |
|
}; |
|
|
|
this.showDogear = function(/*DomNode*/pageNode){ |
|
// summary: |
|
// Shows the dog-ear. |
|
var childNodes = this.getChildren(pageNode);; |
|
domStyle.set(pageNode, "overflow", ""); |
|
childNodes[1] && domStyle.set(childNodes[1], "display", ""); // backNode |
|
childNodes[2] && domStyle.set(childNodes[2], "display", this.turnfrom === "bottom" ? "none" : ""); // shadowNode |
|
}; |
|
|
|
this.hideDogear = function(/*DomNode*/pageNode){ |
|
// summary: |
|
// Hides the dog-ear. |
|
if(this.turnfrom === "bottom"){ return; } |
|
|
|
var childNodes = this.getChildren(pageNode); |
|
domStyle.set(pageNode, "overflow", "visible"); |
|
childNodes[1] && domStyle.set(childNodes[1], "display", "none"); // backNode |
|
childNodes[2] && domStyle.set(childNodes[2], "display", "none"); // shadowNode |
|
}; |
|
}; |
|
});
|
|
|