Last active
May 31, 2020 04:24
-
-
Save INCHMAN1900/d613af76fb3456845d419e955fa811a6 to your computer and use it in GitHub Desktop.
JavaScript snippets to create full page applications.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!-- full page template --> | |
<!-- created for single-page personal website --> | |
<html> | |
<head> | |
<meta charset='utf8'> | |
<style> | |
body { | |
margin: 0; | |
} | |
#app { | |
position: relative; | |
width: 100vw; | |
height: 100vh; | |
overflow: hidden; | |
background-color: lightgreen; | |
} | |
</style> | |
</head> | |
<body> | |
<div id='app'></div> | |
</body> | |
<script type='module'> | |
import createFullPage from './fullPageTemplate.js' | |
createFullPage({ | |
container: '#app', | |
columns: [ | |
{ | |
pages: [ | |
{ | |
render () { | |
return '<h2>page1 title</h2>' | |
} | |
}, | |
{ | |
render () { | |
return '<h2>page1 title2</h2>' | |
} | |
}, | |
] | |
}, | |
{ | |
pages: [ | |
{ | |
render () { | |
return '<h2>page2 title</h2>' | |
} | |
}, | |
{ | |
render () { | |
return '<h2>page2 title2</h2>' | |
} | |
}, | |
] | |
} | |
] | |
}) | |
</script> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A piece of JavaScript snippet to create full page applications. | |
// | |
// To guarentee performance: | |
// Only one page exists when not in transition phase. | |
// Only two pages exist when in transition phase. | |
const defaultConfig = { | |
// Selector, only the first one matched will be used. | |
container: 'body', | |
transitionDuration: '0.3s', | |
// Columns config. Elements in config should be in this form: | |
// { | |
// pages: [ | |
// { | |
// render () { | |
// return '<h2>page title</h2>' | |
// } | |
// }, | |
// ] | |
// } | |
// Render function supports both html string and HTMLElmement. | |
columns: [] | |
} | |
// Supported KeyboardEvent code bindings. | |
// This can be easily extendes. | |
const directions = { | |
ArrowUp: 1, | |
ArrowDown: 2, | |
ArrowLeft: 3, | |
ArrowRight: 4, | |
// Vim key bindings. | |
KeyK: 1, | |
KeyJ: 2, | |
KeyH: 3, | |
KeyL: 4 | |
} | |
// Next page's init position when switching between pages. | |
const nextInitPositions = { | |
[directions.ArrowUp]: ['-100%', '0'], | |
[directions.ArrowDown]: ['100%', '0'], | |
[directions.ArrowLeft]: ['0', '-100%'], | |
[directions.ArrowRight]: ['0', '100%'], | |
} | |
// Current page's target position when switching between pages. | |
const currentTargetPositions = { | |
[directions.ArrowUp]: ['100%', '0'], | |
[directions.ArrowDown]: ['-100%', '0'], | |
[directions.ArrowLeft]: ['0', '100%'], | |
[directions.ArrowRight]: ['0', '-100%'] | |
} | |
function createDiv (classList = []) { | |
const div = document.createElement('div') | |
for (let className of classList) { | |
div.classList.add(className) | |
} | |
return div | |
} | |
function createPage () { | |
const div = createDiv(['page']) | |
div.style = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden;' | |
return div | |
} | |
let transitionEndEvent | |
function getEndEvent () { | |
if (transitionEndEvent) return transitionEndEvent | |
let el = createDiv() | |
const transitions = { | |
'transition':'transitionend', | |
'OTransition':'otransitionend', | |
'MozTransition':'transitionend', | |
'WebkitTransition':'webkitTransitionEnd' | |
} | |
for (let key in transitions) { | |
if (key in el.style) { | |
return (transitionEvent = transitions[key]) | |
} | |
} | |
} | |
function setPosition (container, top, left, transitionDuration) { | |
container.style.transitionDuration = transitionDuration | |
container.style.top = top | |
container.style.left = left | |
} | |
function renderPage (page, content) { | |
if (typeof content === 'string') { | |
page.innerHTML = content | |
} else if (content instanceof HTMLElement) { | |
page.appendChild(content) | |
} else { | |
throw new Error('unsupported render function.') | |
} | |
return page | |
} | |
export default function createFullPage (conf) { | |
let config = Object.assign({}, defaultConfig, conf) | |
const container = document.querySelector(config.container) | |
if (!container) throw new Error('container element does not exist.') | |
container.style.position = 'relative' | |
const cache = new Map() // render cache map, reducing repeating rendering. | |
let columnNo = 0 // current column numner. | |
let pageNo= 0 // current page number in current column | |
let isToggling = false | |
let currentPage = createPage() // current page | |
renderPage(currentPage, config.columns[columnNo].pages[pageNo].render()) | |
container.appendChild(currentPage) | |
async function toggle (direction) { | |
if (isToggling) return | |
isToggling = true | |
const { pages } = config.columns[columnNo] | |
let incoming | |
switch (direction) { | |
case directions.ArrowUp: | |
pageNo > 0 && (incoming = pages[--pageNo]) | |
break | |
case directions.ArrowDown: | |
pageNo < pages.length - 1 && (incoming = pages[++pageNo]) | |
break | |
case directions.ArrowLeft: | |
if (columnNo === 0) break | |
pageNo = 0 | |
incoming = config.columns[--columnNo].pages[pageNo] | |
break | |
case directions.ArrowRight: | |
if (columnNo === config.columns.length - 1) break | |
pageNo = 0 | |
incoming = config.columns[++columnNo].pages[pageNo] | |
break | |
} | |
if (!incoming) { | |
isToggling = false | |
return | |
} | |
const cacheKey = [columnNo, pageNo].join('-') | |
let renderResult | |
if (cache.has(cacheKey)) { | |
renderResult = cache.get(cacheKey) | |
} else { | |
renderResult = incoming.render() | |
cache.set(cacheKey, renderResult) | |
} | |
const [initPosition, currentPosition] = [nextInitPositions[direction], currentTargetPositions[direction]] | |
const nextPage = createPage() | |
setPosition(nextPage, initPosition[0], initPosition[1]) | |
renderPage(nextPage, renderResult) | |
container.appendChild(nextPage) | |
setTimeout(() => { | |
setPosition(currentPage, currentPosition[0], currentPosition[1], config.transitionDuration) | |
setPosition(nextPage, 0, 0, config.transitionDuration) | |
}, 0) | |
const event = getEndEvent() | |
const reset = () => { | |
container.removeChild(currentPage) | |
currentPage = nextPage | |
currentPage.removeEventListener(event, reset) | |
isToggling = false | |
} | |
nextPage.addEventListener(event, reset) | |
} | |
function listen (e) { | |
const { code } = e | |
if (directions[code]) { | |
toggle(directions[code]) | |
} | |
} | |
// Use body to listen the keydown events. | |
document.body.addEventListener('keydown', listen) | |
return { | |
destroy () { | |
document.body.removeEventListener('keydown', listen) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment