You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

311 lines
8.8 KiB
JavaScript

1 year ago
import curves from './config/curves'
const defaultTransitionBC = 'linear'
/**
* @description Get the N-frame animation state by the start and end state
* of the animation and the easing curve
* @param {String|Array} tBC Easing curve name or data
* @param {Number|Array|Object} startState Animation start state
* @param {Number|Array|Object} endState Animation end state
* @param {Number} frameNum Number of Animation frames
* @param {Boolean} deep Whether to use recursive mode
* @return {Array|Boolean} State of each frame of the animation (Invalid input will return false)
*/
export function transition (tBC, startState = null, endState = null, frameNum = 30, deep = false) {
if (!checkParams(...arguments)) return false
try {
// Get the transition bezier curve
const bezierCurve = getBezierCurve(tBC)
// Get the progress of each frame state
const frameStateProgress = getFrameStateProgress(bezierCurve, frameNum)
// If the recursion mode is not enabled or the state type is Number, the shallow state calculation is performed directly.
if (!deep || typeof endState === 'number') return getTransitionState(startState, endState, frameStateProgress)
return recursionTransitionState(startState, endState, frameStateProgress)
} catch(e) {
console.warn('Transition parameter may be abnormal!')
return [endState]
}
}
/**
* @description Check if the parameters are legal
* @param {String} tBC Name of transition bezier curve
* @param {Any} startState Transition start state
* @param {Any} endState Transition end state
* @param {Number} frameNum Number of transition frames
* @return {Boolean} Is the parameter legal
*/
function checkParams (tBC, startState = false, endState = false, frameNum = 30) {
if (!tBC || startState === false || endState === false || !frameNum) {
console.error('transition: Missing Parameters!')
return false
}
if (typeof startState !== typeof endState) {
console.error('transition: Inconsistent Status Types!')
return false
}
const stateType = typeof endState
if (stateType === 'string' || stateType === 'boolean' || !tBC.length) {
console.error('transition: Unsupported Data Type of State!')
return false
}
if (!curves.has(tBC) && !(tBC instanceof Array)) {
// console.warn('transition: Transition curve not found, default curve will be used!')
}
return true
}
/**
* @description Get the transition bezier curve
* @param {String} tBC Name of transition bezier curve
* @return {Array} Bezier curve data
*/
function getBezierCurve (tBC) {
let bezierCurve = ''
if (curves.has(tBC)) {
bezierCurve = curves.get(tBC)
} else if (tBC instanceof Array) {
bezierCurve = tBC
} else {
bezierCurve = curves.get(defaultTransitionBC)
}
return bezierCurve
}
/**
* @description Get the progress of each frame state
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} frameNum Number of transition frames
* @return {Array} Progress of each frame state
*/
function getFrameStateProgress (bezierCurve, frameNum) {
const tMinus = 1 / (frameNum - 1)
const tState = new Array(frameNum).fill(0).map((t, i) => i * tMinus)
const frameState = tState.map(t => getFrameStateFromT(bezierCurve, t))
return frameState
}
/**
* @description Get the progress of the corresponding frame according to t
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} t Current frame t
* @return {Number} Progress of current frame
*/
function getFrameStateFromT (bezierCurve, t) {
const tBezierCurvePoint = getBezierCurvePointFromT(bezierCurve, t)
const bezierCurvePointT = getBezierCurvePointTFromReT(tBezierCurvePoint, t)
return getBezierCurveTState(tBezierCurvePoint, bezierCurvePointT)
}
/**
* @description Get the corresponding sub-curve according to t
* @param {Array} bezierCurve Transition bezier curve
* @param {Number} t Current frame t
* @return {Array} Sub-curve of t
*/
function getBezierCurvePointFromT (bezierCurve, t) {
const lastIndex = bezierCurve.length - 1
let [begin, end] = ['', '']
bezierCurve.findIndex((item, i) => {
if (i === lastIndex) return
begin = item
end = bezierCurve[i + 1]
const currentMainPointX = begin[0][0]
const nextMainPointX = end[0][0]
return t >= currentMainPointX && t < nextMainPointX
})
const p0 = begin[0]
const p1 = begin[2] || begin[0]
const p2 = end[1] || end[0]
const p3 = end[0]
return [p0, p1, p2, p3]
}
/**
* @description Get local t based on t and sub-curve
* @param {Array} bezierCurve Sub-curve
* @param {Number} t Current frame t
* @return {Number} local t of sub-curve
*/
function getBezierCurvePointTFromReT (bezierCurve, t) {
const reBeginX = bezierCurve[0][0]
const reEndX = bezierCurve[3][0]
const xMinus = reEndX - reBeginX
const tMinus = t - reBeginX
return tMinus / xMinus
}
/**
* @description Get the curve progress of t
* @param {Array} bezierCurve Sub-curve
* @param {Number} t Current frame t
* @return {Number} Progress of current frame
*/
function getBezierCurveTState ([[, p0], [, p1], [, p2], [, p3]], t) {
const { pow } = Math
const tMinus = 1 - t
const result1 = p0 * pow(tMinus, 3)
const result2 = 3 * p1 * t * pow(tMinus, 2)
const result3 = 3 * p2 * pow(t, 2) * tMinus
const result4 = p3 * pow(t, 3)
return 1 - (result1 + result2 + result3 + result4)
}
/**
* @description Get transition state according to frame progress
* @param {Any} startState Transition start state
* @param {Any} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getTransitionState (begin, end, frameState) {
let stateType = 'object'
if (typeof begin === 'number') stateType = 'number'
if (begin instanceof Array) stateType = 'array'
if (stateType === 'number') return getNumberTransitionState(begin, end, frameState)
if (stateType === 'array') return getArrayTransitionState(begin, end, frameState)
if (stateType === 'object') return getObjectTransitionState(begin, end, frameState)
return frameState.map(t => end)
}
/**
* @description Get the transition data of the number type
* @param {Number} startState Transition start state
* @param {Number} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getNumberTransitionState (begin, end, frameState) {
const minus = end - begin
return frameState.map(s => begin + minus * s)
}
/**
* @description Get the transition data of the array type
* @param {Array} startState Transition start state
* @param {Array} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getArrayTransitionState (begin, end, frameState) {
const minus = end.map((v, i) => {
if (typeof v !== 'number') return false
return v - begin[i]
})
return frameState.map(s =>
minus.map((v, i) => {
if (v === false) return end[i]
return begin[i] + v * s
}))
}
/**
* @description Get the transition data of the object type
* @param {Object} startState Transition start state
* @param {Object} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function getObjectTransitionState (begin, end, frameState) {
const keys = Object.keys(end)
const beginValue = keys.map(k => begin[k])
const endValue = keys.map(k => end[k])
const arrayState = getArrayTransitionState(beginValue, endValue, frameState)
return arrayState.map(item => {
const frameData = {}
item.forEach((v, i) => (frameData[keys[i]] = v))
return frameData
})
}
/**
* @description Get the transition state data by recursion
* @param {Array|Object} startState Transition start state
* @param {Array|Object} endState Transition end state
* @param {Array} frameState Frame state progress
* @return {Array} Transition frame state
*/
function recursionTransitionState (begin, end, frameState) {
const state = getTransitionState(begin, end, frameState)
for (let key in end) {
const bTemp = begin[key]
const eTemp = end[key]
if (typeof eTemp !== 'object') continue
const data = recursionTransitionState(bTemp, eTemp, frameState)
state.forEach((fs, i) => (fs[key] = data[i]))
}
return state
}
/**
* @description Inject new curve into curves as config
* @param {Any} key The key of curve
* @param {Array} curve Bezier curve data
* @return {Undefined} No return
*/
export function injectNewCurve (key, curve) {
if (!key || !curve) {
console.error('InjectNewCurve Missing Parameters!')
return
}
curves.set(key, curve)
}
export default transition