Skip to content

Instantly share code, notes, and snippets.

@INCHMAN1900
Last active May 31, 2020 04:24
Show Gist options
  • Save INCHMAN1900/d613af76fb3456845d419e955fa811a6 to your computer and use it in GitHub Desktop.
Save INCHMAN1900/d613af76fb3456845d419e955fa811a6 to your computer and use it in GitHub Desktop.
JavaScript snippets to create full page applications.
<!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>
// 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