tgweb
Version:
Teamgenik Website Builder Offline Tool
460 lines (391 loc) • 13.4 kB
JavaScript
const modulo = (v, w) => (v % w + w) % w
const getScreenHeight = (kernel) => {
if (kernel) {
const rect = kernel.getBoundingClientRect()
return rect.height
}
else {
return window.innerHeight
}
}
const getTramProgress = (kernel, tram) => {
if (kernel) {
const rect = kernel.getBoundingClientRect()
return rect.height + rect.y - tram.y
}
else {
return window.innerHeight - tram.y
}
}
const getPositionPair = (kernel, target, tram, progress, advancing) => {
const positionPairs =
Object.keys(target.dataset)
.map(key => {
const md = key.match(/^tram(Forward|Backward)-(\d{1,3})(|%|vh|px)(|[+-])$/)
if (md === null) return
if (advancing && md[1] === "Backward" || !advancing && md[1] === "Forward") return
const realDistance = _getRealDistance(kernel, tram, md[2], md[3], md[4])
return [key, realDistance]
})
.filter(pair => pair !== undefined)
return(
advancing ?
positionPairs.sort((a, b) => b[1] - a[1]).find(p => p[1] <= progress) :
positionPairs.sort((a, b) => a[1] - b[1]).find(p => p[1] >= progress)
)
}
const _getRealDistance = (kernel, tram, distance, unit, suffix) => {
const screenHeight = getScreenHeight(kernel)
let realDistance
if (unit === "px") realDistance = distance
else if (unit === "%") realDistance = tram.height * distance / 100
else if (unit === "vh") realDistance = screenHeight * distance / 100
else realDistance = (screenHeight + tram.height) * distance / 100
if (suffix === "+") realDistance = window.innerHeight + realDistance
else if (suffix === "-") realDistance = window.innerHeight - realDistance
return realDistance
}
const withinRange = (progress, previousProgress, longDistance) => {
if (progress >= 0 && progress <= longDistance) return true
if (previousProgress === undefined) return false
if (previousProgress >= 0 && previousProgress <= longDistance) return true
return false
}
window.tgweb = {
switcher: (el, interval, transitionDuration) => ({
el,
interval,
transitionDuration,
len: undefined,
initial: true,
inTransition: false,
i: 0,
v: undefined,
init() {
this.body = this.el.querySelector("[data-switcher-body]")
if (this.body === null) {
console.error("This switcher does not have body.")
return
}
const items = this.body.querySelectorAll("[data-item-index]")
const firstItem = items[0]
if (firstItem === undefined) {
console.error("This switcher has no item.")
return
}
this.len = items.length
this.body.style.display = "flex"
this.body.style.flexDirection = "column"
this.body.style.position = "relative"
this.body.style.width = firstItem.offsetWidth + "px"
this.body.style.height = firstItem.offsetHeight + "px"
const windowResizeHandler = () => {
const items = this.body.querySelectorAll("[data-item-index]")
const firstItem = items[0]
this.body.style.width = firstItem.offsetWidth + "px"
this.body.style.height = firstItem.offsetHeight + "px"
}
window.tgweb.windowResizeHandlers.push(windowResizeHandler)
if (this.interval > 0) {
this.v = setInterval(() => { this._forward() }, this.interval)
}
},
_forward() {
this._transition()
this.i = this.i < this.len - 1 ? this.i + 1 : this.i
if (this.i == this.len - 1) clearInterval(this.v)
},
first() {
this._transition()
this.i = 0
clearInterval(this.v)
},
prev() {
this._transition()
this.i = this.i > 0 ? this.i - 1 : this.i
clearInterval(this.v)
},
next() {
this._transition()
this.i = this.i < this.len - 1 ? this.i + 1 : this.i
clearInterval(this.v)
},
last() {
this._transition()
this.i = this.len - 1
clearInterval(this.v)
},
choose(n) {
this._transition()
if (n >= 0 && n < this.len) this.i = n
clearInterval(this.v)
},
_transition() {
this.initial = false
if (this.transitionDuration > 0) {
this.inTransition = true
setTimeout(() => { this.inTransition = false }, this.transitionDuration + 250)
}
}
}),
rotator: (el, interval, transitionDuration) => ({
el,
interval,
transitionDuration,
len: undefined,
initial: true,
inTransition: false,
i: 0,
v: undefined,
init() {
this.body = this.el.querySelector("[data-rotator-body]")
if (this.body === null) {
console.error("This rotator does not have body.")
return
}
const items = this.body.querySelectorAll("[data-item-index]")
const firstItem = items[0]
if (firstItem === undefined) {
console.error("This switcher has no item.")
return
}
this.len = items.length
this.body.style.display = "flex"
this.body.style.flexDirection = "column"
this.body.style.position = "relative"
this.body.style.width = firstItem.offsetWidth + "px"
this.body.style.height = firstItem.offsetHeight + "px"
const windowResizeHandler = () => {
const items = this.body.querySelectorAll("[data-item-index]")
const firstItem = items[0]
this.body.style.width = firstItem.offsetWidth + "px"
this.body.style.height = firstItem.offsetHeight + "px"
}
window.tgweb.windowResizeHandlers.push(windowResizeHandler)
if (this.interval > 0) {
this.v = setInterval(() => { this._forward() }, this.interval)
}
},
_forward() {
this._transition()
this.i = this.i < this.len - 1 ? this.i + 1 : 0
},
first() {
this._transition()
this.i = 0
clearInterval(this.v)
},
last() {
this._transition()
this.i = this.len - 1
clearInterval(this.v)
},
prev() {
this._transition()
this.i = this.i > 0 ? this.i - 1 : this.len - 1
clearInterval(this.v)
},
next() {
this._transition()
this.i = this.i < this.len - 1 ? this.i + 1 : 0
clearInterval(this.v)
},
choose(n) {
this._transition()
if (n >= 0 && n < this.len) this.i = n
clearInterval(this.v)
},
_transition() {
this.initial = false
if (this.transitionDuration > 0) {
this.inTransition = true
setTimeout(() => { this.inTransition = false }, this.transitionDuration + 250)
}
}
}),
carousel: (el, len, repeatCount, interval, transitionDuration) => ({
el,
len,
repeatCount,
interval,
transitionDuration,
inTransition: false,
i: 0,
frame: undefined,
body: undefined,
itemWidth: undefined,
init() {
this.frame = this.el.querySelector("[data-carousel-frame]")
this.body = this.el.querySelector("[data-carousel-body]")
if (this.frame === null || this.body === null) {
console.error("This carousel does not have frame and body.")
return
}
const items = this.body.querySelectorAll("[data-carousel-item]")
const firstItem = items[0]
if (firstItem === undefined) {
console.error("This carousel has no item.")
return
}
this.itemWidth = firstItem.offsetWidth
this.frame.style.overflow = "hidden"
this.body.style.display = "flex"
this.body.style.width = String(this.itemWidth * this.len * this.repeatCount) + "px"
Array.from(items).forEach(item => {
item.style.display = "block"
item.style.visibility = "visible"
})
this._resetStyle()
const windowResizeHandler = () => {
this.body.style.display = "block"
this.body.style.width = "auto"
this.itemWidth = firstItem.offsetWidth
this.body.style.display = "flex"
this.body.style.width = String(this.itemWidth * this.len * this.repeatCount) + "px"
this._resetStyle()
}
window.tgweb.windowResizeHandlers.push(windowResizeHandler)
if (this.interval > 0) this.v = setInterval(() => { this._forward() }, this.interval)
},
_forward() {
this._shiftPosition(1)
setTimeout(() => {
this.i = this.i < this.len - 1 ? this.i + 1 : 0
this._resetStyle()
}, this.transitionDuration + 250)
},
prev() {
if (this.inTransition) return
if (this.v) clearInterval(this.v)
this._shiftPosition(-1)
setTimeout(() => {
this.i = this.i > 0 ? this.i - 1 : this.len - 1
this._resetStyle()
}, this.transitionDuration + 250)
},
next() {
if (this.inTransition) return
if (this.v) clearInterval(this.v)
this._shiftPosition(1)
setTimeout(() => {
this.i = this.i < this.len - 1 ? this.i + 1 : 0
this._resetStyle()
}, this.transitionDuration + 250)
},
choose(n) {
if (this.inTransition) return
if (this.i === n) return
if (this.v) clearInterval(this.v)
this._shiftPosition(n - this.i)
setTimeout(() => {
this.i = n
this._resetStyle()
}, this.transitionDuration + 250)
},
_shiftPosition(diff) {
this.body.style.transitionProperty = "translate"
this.body.style.transitionDuration = this.transitionDuration + "ms"
const translateLength =
this.itemWidth * (4 + diff) - (this.frame.offsetWidth - this.itemWidth) / 2
this.body.style.translate = `-${translateLength}px 0`
this.inTransition = true
},
_resetStyle() {
this.body.style.transitionProperty = "none"
this.body.style.transitionDuration = "0s"
const translateLength = this.itemWidth * 4 - (this.frame.offsetWidth - this.itemWidth) / 2
this.body.style.translate = `-${translateLength}px 0`
this.body.querySelectorAll("[data-carousel-item]").forEach((item, n) => {
item.style.order = modulo(n - this.i + 4, this.len * this.repeatCount)
})
this.inTransition = false
}
}),
scheduler: (el) => ({
el,
init() {
this.el.dataset.schedulerBaseClass = this.el.className
if (this.el.dataset.schedulerInit)
this.el.dataset.schedulerInit.split(" ").forEach(token => this.el.classList.add(token))
for (const attr of this.el.attributes) {
const md = attr.name.match(/^data-scheduler-(\d+)$/)
if (md) {
const delay = parseInt(md[1])
const tokens = this.el.getAttribute(attr.name).split(" ")
setTimeout(() => {
this.el.className = this.el.dataset.schedulerBaseClass
tokens.forEach(token => this.el.classList.add(token))
}, delay)
}
}
}
}),
tram: (el) => ({
el,
kernel: undefined,
previousProgress: undefined,
targets: [],
init() {
this.targets = Array.from(this.el.querySelectorAll("[data-tram-trigger]"))
if (this.el.dataset.tramTrigger !== undefined) this.targets.push(el)
this.kernel = window.document.getElementById("tg-preview-area-kernel")
if (this.kernel) {
this._doInit()
}
else {
const body = window.document.querySelector("body[phx-hook='Main']")
if (body) this._doInit()
else window.addEventListener("load", () => this._doInit())
}
},
_doInit() {
this._initializeTargets()
this._processTriggers()
const target = this.kernel ? this.kernel : window.document
target.addEventListener("scroll", () => {
this._processTriggers()
})
},
_initializeTargets() {
const tram = this.el.getBoundingClientRect()
this.targets.forEach(target => {
const progress = getTramProgress(this.kernel, tram)
const positionPair = getPositionPair(this.kernel, target, tram, progress, true)
if (positionPair === undefined) {
if (target.dataset.tramInit !== undefined) {
const classTokens = target.dataset.tramInit.split(/\s+/)
target.classList.add(...classTokens)
}
}
else {
const attrValue = target.dataset[positionPair[0]]
const additionalClassTokens = attrValue.split(/\s+/)
target.className = target.dataset.tramBaseClass
target.classList.add(...additionalClassTokens)
}
})
},
_processTriggers() {
const tram = this.el.getBoundingClientRect()
const longDistance = getScreenHeight(this.kernel) + tram.height
const progress = getTramProgress(this.kernel, tram)
if (withinRange(progress, this.previousProgress, longDistance)) {
const advancing = this.previousProgress === undefined || progress > this.previousProgress
this.targets.forEach(target => {
const positionPair = getPositionPair(this.kernel, target, tram, progress, advancing)
if (positionPair === undefined) return
const attrName = positionPair[0]
const attrValue = target.dataset[attrName]
const additionalClassTokens = attrValue.split(/\s+/)
target.className = target.dataset.tramBaseClass
target.classList.add(...additionalClassTokens)
})
}
this.previousProgress = progress
}
}),
windowResizeHandlers: []
}
window.onresize = () => {
this.tgweb.windowResizeHandlers.forEach(handler => handler())
}