iportal
Version:
web-portal
270 lines (257 loc) • 7.59 kB
text/typescript
'use strict'
// 合抱之木,生於毫末;九层之台,起於累土;千里之行,始於足下。
import ease from './ease'
import { EventProvider } from '../Event'
const rAF = requestAnimationFrame ?? setTimeout
const gCS = getComputedStyle
/* Animate */
class Animate extends EventProvider {
public el!: HTMLElement
private _props = {}
private _transforms = {}
private _transforming = false
private _proper: Array<object> = []
private _caller: Array<Function> = []
private _transitionProps: Array<string> = []
constructor (el: HTMLElement) {
super()
if (!el) return
this.el = el
this.el.style.setProperty('transition-duration', '0ms')
}
public transform (transform: string) {
this.transition('transform')
const propName = (transform.match(/\w+\b/) || [])[0]
if (propName) this._transforms[propName] = transform
return this
}
public skew (x: number, y: number) {
return this.transform('skew(' + x + 'deg, ' + (y || 0) + 'deg)')
}
public skewX (n: number) {
return this.transform('skewX(' + n + 'deg)')
}
public skewY (n: number) {
return this.transform('skewY(' + n + 'deg)')
}
public to (x: number = 0, y: number = 0, z: number = 0) {
// 3d set
this.transform('translate3d(' + (x ? x + 'px' : 0) + ',' + (y ? y + 'px' : 0) + ',' + (z ? z + 'px' : 0) + ')')
return this
}
public translate = this.to
public translate3d = this.to
public x (n: number) {
return this.transform('translateX(' + n + 'px)')
}
public translateX = this.x
public y (n: number) {
return this.transform('translateY(' + n + 'px)')
}
public translateY = this.y
public z (z: number) {
return this.transform('translateZ(' + z + 'px)')
}
public translateZ = this.z
public scale (x: number, y: number = x) {
return this.transform('scale(' +
x + ', ' +
(y || x) +
')')
}
public opacity (o: number) {
this.transition('opacity')
return this.style('opacity', o)
}
public scaleX (n: number) {
return this.transform('scaleX(' + n + ')')
}
public matrix (m11: number, m12: number, m21: number, m22: number, m31: number, m32: number) {
return this.transform('matrix(' + [m11, m12, m21, m22, m31, m32].join(',') + ')')
}
public scaleY (n: number) {
return this.transform('scaleY(' + n + ')')
}
public rotate (n: number) {
return this.transform('rotate(' + n + 'deg)')
}
public rotateX (n: number) {
return this.transform('rotateX(' + n + 'deg)')
}
public rotateY (n: number) {
return this.transform('rotateY(' + n + 'deg)')
}
public rotateZ (n: number) {
return this.transform('rotateZ(' + n + 'deg)')
}
public rotate3d (x: number, y: number, z: number, d: number) {
return this.transform('rotate3d(' + x + ', ' + y + ',' + z + ',' + d + 'deg)')
}
public perspective (z: number) {
const box = this.el.parentElement
if (box) {
box.style.setProperty('transform-style', 'preserve-3d')
box.style.setProperty('perspective', z + 'px')
}
return this
}
public backface (visibility: boolean = true) {
return this.style('backface-visibility', visibility ? 'visible' : 'hidden')
}
public ease (s: string) {
s = ease[s as string] || s || 'ease'
return this.style('transition-timing-function', s)
}
public animate (name: string, props: object) {
for (const i in props) {
if (props.hasOwnProperty(i)) {
this.style('animation-' + i, props[i])
}
}
return this.style('animation-name', name)
}
public duration (n: string | number) {
n = 'string' === typeof n ?
parseFloat(n) * 1000 :
n
return this.style('transition-duration', n + 'ms')
}
public getDuration () {
return !!parseFloat(gCS(this.el).transitionDuration)
}
public delay (n: string | number) {
n = 'string' === typeof n ?
parseFloat(n) * 1000 :
n
return this.style('transition-delay', n + 'ms')
}
public origin (x: number | string | Array<number>, y: number = 0) {
let n = 'center'
if (Array.isArray(x)) {
y = x[1] || 0
x = x[0] || 0
}
if (typeof x === 'string') {
n = x
} else if (typeof x === 'number') {
n = x + 'px' + ' ' + y + 'px'
}
return this.style('transform-origin', n)
}
public width (val?: number) {
this.transition('width')
return this.style('width', val === undefined ? '' : val + 'px')
}
public height (val?: number) {
this.transition('height')
return this.style('height', val === undefined ? '' : val + 'px')
}
public add (prop: string, val: number) {
return this.on('start', () => {
const curr = parseInt(this.current(prop), 10)
this.style(prop, curr + val + 'px')
})
}
public subc (prop: string, val: number) {
return this.on('start', () => {
const curr = parseInt(this.current(prop), 10)
this.style(prop, curr - val + 'px')
})
}
public current (prop: string) {
return gCS(this.el).getPropertyValue(prop)
}
public transition (prop: string) {
if (this._transitionProps.indexOf(prop) === -1) this._transitionProps.push(prop)
return this
}
public filter (val: string) {
this.style('filter', val)
this.transition('filter')
return this
}
public style (prop: string, val?: string | number) {
this._props[prop] = val === undefined ? '' : val
return this
}
private onceTransitionend (fn: () => void) {
if (!this.getDuration()) return setTimeout(fn, 0)
const onec = (e: TransitionEvent) => {
if (e.target !== this.el) return false
fn()
this.el.removeEventListener('transitionend', onec, false)
}
this.el.addEventListener('transitionend', onec, false)
}
private applyTransform () {
const transform: Array<string> = []
if (!this._transforms['translate3d'] && !this._transforms['translateZ']) {
this._transforms['translateZ'] = 'translateZ(0)'
}
for (const i in this._transforms) {
transform.push(this._transforms[i])
}
if (transform.length) {
this.style('transform', transform.join(' '))
}
return this
}
public applyProperties () {
const prop = this._proper.shift()
this._transforming = true
for (const name in prop) {
this.el.style.setProperty(name, prop[name])
}
this.onceTransitionend(() => {
this.clear()
this.next()
})
return this
}
public next () {
if (this._caller.length) {
const fn = this._caller.shift()
fn?.()
}
if (this._caller.length === 0) {
this.init()
} else {
rAF(() => { this.applyProperties() })
}
}
public clear () {
this._transforms = {}
}
public init () {
if (this.getDuration()) {
this.el.style.setProperty('transition-duration', '0ms')
}
this.clear()
this._transforming = false
return this
}
public then (fn: () => void = () => undefined) {
this.applyTransform()
this.style('transition-property', this._transitionProps.join(', '))
this._proper.push(this._props)
this._props = {}
this._caller.push(() => fn?.call(this))
return this
}
public and (fn: () => void = () => undefined) {
this.then(fn)
if (this._transforming) return this
rAF(() => { this.applyProperties() })
}
public end (fn?: () => void) {
return new Promise<void>((resolve) => {
fn = fn ?? (() => resolve())
this.then(fn)
if (this._transforming) return
rAF(() => { this.applyProperties() })
})
}
}
export {
Animate
}