engine-fork
Version:
JavaScript animation engine
1,183 lines (1,052 loc) • 32.3 kB
JavaScript
// /**
// * http://animejs.com
// * JavaScript animation engine
// * @version v2.2.0
// * @author Julian Garnier
// * @copyright ©2017 Julian Garnier
// * Released under the MIT license
// **/
//
/**
* http://animejs.com
* JavaScript animation engine
* @version v2.2.0
* @author Julian Garnier
* @copyright ©2017 Julian Garnier
* Released under the MIT license
**/
;(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory)
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory()
} else {
// Browser globals (root is window)
root.anime = factory()
}
})(this, () => {
// Defaults
const defaultInstanceSettings = {
update: undefined,
begin: undefined,
run: undefined,
complete: undefined,
loop: 1,
direction: 'normal',
autoplay: true,
offset: 0,
}
const defaultTweenSettings = {
duration: 1000,
delay: 0,
easing: 'easeOutElastic',
elasticity: 500,
round: 0,
}
const validTransforms = [
'translateX',
'translateY',
'translateZ',
'rotate',
'rotateX',
'rotateY',
'rotateZ',
'scale',
'scaleX',
'scaleY',
'scaleZ',
'skewX',
'skewY',
'perspective',
]
let transformString
// Utils
function stringContains(str, text) {
return str.indexOf(text) > -1
}
const is = {
arr: a => Array.isArray(a),
obj: a => stringContains(Object.prototype.toString.call(a), 'Object'),
pth: a => is.obj(a) && a.hasOwnProperty('totalLength'),
svg: a => a instanceof SVGElement,
dom: a => a.nodeType || is.svg(a),
str: a => typeof a === 'string',
fnc: a => typeof a === 'function',
und: a => typeof a === 'undefined',
hex: a => /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a),
rgb: a => /^rgb/.test(a),
hsl: a => /^hsl/.test(a),
col: a => is.hex(a) || is.rgb(a) || is.hsl(a),
}
// BezierEasing https://github.com/gre/bezier-easing
const bezier = (() => {
const kSplineTableSize = 11
const kSampleStepSize = 1.0 / (kSplineTableSize - 1.0)
function A(aA1, aA2) {
return 1.0 - 3.0 * aA2 + 3.0 * aA1
}
function B(aA1, aA2) {
return 3.0 * aA2 - 6.0 * aA1
}
function C(aA1) {
return 3.0 * aA1
}
function calcBezier(aT, aA1, aA2) {
return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT
}
function getSlope(aT, aA1, aA2) {
return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1)
}
function binarySubdivide(aX, aA, aB, mX1, mX2) {
let currentX,
currentT,
i = 0
do {
currentT = aA + (aB - aA) / 2.0
currentX = calcBezier(currentT, mX1, mX2) - aX
if (currentX > 0.0) {
aB = currentT
} else {
aA = currentT
}
} while (Math.abs(currentX) > 0.0000001 && ++i < 10)
return currentT
}
function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
for (let i = 0; i < 4; ++i) {
const currentSlope = getSlope(aGuessT, mX1, mX2)
if (currentSlope === 0.0) return aGuessT
const currentX = calcBezier(aGuessT, mX1, mX2) - aX
aGuessT -= currentX / currentSlope
}
return aGuessT
}
function bezier(mX1, mY1, mX2, mY2) {
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) return
let sampleValues = new Float32Array(kSplineTableSize)
if (mX1 !== mY1 || mX2 !== mY2) {
for (let i = 0; i < kSplineTableSize; ++i) {
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2)
}
}
function getTForX(aX) {
let intervalStart = 0.0
let currentSample = 1
const lastSample = kSplineTableSize - 1
for (
;
currentSample !== lastSample && sampleValues[currentSample] <= aX;
++currentSample
) {
intervalStart += kSampleStepSize
}
--currentSample
const dist =
(aX - sampleValues[currentSample]) /
(sampleValues[currentSample + 1] - sampleValues[currentSample])
const guessForT = intervalStart + dist * kSampleStepSize
const initialSlope = getSlope(guessForT, mX1, mX2)
if (initialSlope >= 0.001) {
return newtonRaphsonIterate(aX, guessForT, mX1, mX2)
} else if (initialSlope === 0.0) {
return guessForT
} else {
return binarySubdivide(
aX,
intervalStart,
intervalStart + kSampleStepSize,
mX1,
mX2
)
}
}
return x => {
if (mX1 === mY1 && mX2 === mY2) return x
if (x === 0) return 0
if (x === 1) return 1
return calcBezier(getTForX(x), mY1, mY2)
}
}
return bezier
})()
const easings = (() => {
const names = [
'Quad',
'Cubic',
'Quart',
'Quint',
'Sine',
'Expo',
'Circ',
'Back',
'Elastic',
]
// Elastic easing adapted from jQueryUI http://api.jqueryui.com/easings/
function elastic(t, p) {
return t === 0 || t === 1
? t
: -Math.pow(2, 10 * (t - 1)) *
Math.sin(
(t - 1 - p / (Math.PI * 2.0) * Math.asin(1)) * (Math.PI * 2) / p
)
}
// Approximated Penner equations http://matthewlein.com/ceaser/
const equations = {
In: [
[0.55, 0.085, 0.68, 0.53] /* InQuad */,
[0.55, 0.055, 0.675, 0.19] /* InCubic */,
[0.895, 0.03, 0.685, 0.22] /* InQuart */,
[0.755, 0.05, 0.855, 0.06] /* InQuint */,
[0.47, 0.0, 0.745, 0.715] /* InSine */,
[0.95, 0.05, 0.795, 0.035] /* InExpo */,
[0.6, 0.04, 0.98, 0.335] /* InCirc */,
[0.6, -0.28, 0.735, 0.045] /* InBack */,
elastic /* InElastic */,
],
Out: [
[0.25, 0.46, 0.45, 0.94] /* OutQuad */,
[0.215, 0.61, 0.355, 1.0] /* OutCubic */,
[0.165, 0.84, 0.44, 1.0] /* OutQuart */,
[0.23, 1.0, 0.32, 1.0] /* OutQuint */,
[0.39, 0.575, 0.565, 1.0] /* OutSine */,
[0.19, 1.0, 0.22, 1.0] /* OutExpo */,
[0.075, 0.82, 0.165, 1.0] /* OutCirc */,
[0.175, 0.885, 0.32, 1.275] /* OutBack */,
(t, f) => 1 - elastic(1 - t, f) /* OutElastic */,
],
InOut: [
[0.455, 0.03, 0.515, 0.955] /* InOutQuad */,
[0.645, 0.045, 0.355, 1.0] /* InOutCubic */,
[0.77, 0.0, 0.175, 1.0] /* InOutQuart */,
[0.86, 0.0, 0.07, 1.0] /* InOutQuint */,
[0.445, 0.05, 0.55, 0.95] /* InOutSine */,
[1.0, 0.0, 0.0, 1.0] /* InOutExpo */,
[0.785, 0.135, 0.15, 0.86] /* InOutCirc */,
[0.68, -0.55, 0.265, 1.55] /* InOutBack */,
(t, f) =>
t < 0.5
? elastic(t * 2, f) / 2
: 1 - elastic(t * -2 + 2, f) / 2 /* InOutElastic */,
],
}
let functions = {
linear: bezier(0.25, 0.25, 0.75, 0.75),
}
for (let type in equations) {
equations[type].forEach((f, i) => {
functions['ease' + type + names[i]] = is.fnc(f)
? f
: bezier.apply(this, f)
})
}
return functions
})()
// Strings
function stringToHyphens(str) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
function selectString(str) {
if (is.col(str)) return
try {
let elements = document.querySelectorAll(str)
return elements
} catch (e) {
return
}
}
// Arrays
function filterArray(arr, callback) {
const len = arr.length
const thisArg = arguments.length >= 2 ? arguments[1] : void 0
let result = []
for (let i = 0; i < len; i++) {
if (i in arr) {
const val = arr[i]
if (callback.call(thisArg, val, i, arr)) {
result.push(val)
}
}
}
return result
}
function flattenArray(arr) {
return arr.reduce((a, b) => a.concat(is.arr(b) ? flattenArray(b) : b), [])
}
function toArray(o) {
if (is.arr(o)) return o
if (is.str(o)) o = selectString(o) || o
if (o instanceof NodeList || o instanceof HTMLCollection)
return [].slice.call(o)
return [o]
}
function arrayContains(arr, val) {
return arr.some(a => a === val)
}
// Objects
function cloneObject(o) {
let clone = {}
for (let p in o) clone[p] = o[p]
return clone
}
function replaceObjectProps(o1, o2) {
let o = cloneObject(o1)
for (let p in o1) o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p]
return o
}
function mergeObjects(o1, o2) {
let o = cloneObject(o1)
for (let p in o2) o[p] = is.und(o1[p]) ? o2[p] : o1[p]
return o
}
// Colors
function rgbToRgba(rgbValue) {
const rgb = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(rgbValue)
return rgb ? `rgba(${rgb[1]},1)` : rgbValue
}
function hexToRgba(hexValue) {
const rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
const hex = hexValue.replace(rgx, (m, r, g, b) => r + r + g + g + b + b)
const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
const r = parseInt(rgb[1], 16)
const g = parseInt(rgb[2], 16)
const b = parseInt(rgb[3], 16)
return `rgba(${r},${g},${b},1)`
}
function hslToRgba(hslValue) {
const hsl =
/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue) ||
/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(hslValue)
const h = parseInt(hsl[1]) / 360
const s = parseInt(hsl[2]) / 100
const l = parseInt(hsl[3]) / 100
const a = hsl[4] || 1
function hue2rgb(p, q, t) {
if (t < 0) t += 1
if (t > 1) t -= 1
if (t < 1 / 6) return p + (q - p) * 6 * t
if (t < 1 / 2) return q
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
return p
}
let r, g, b
if (s == 0) {
r = g = b = l
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
r = hue2rgb(p, q, h + 1 / 3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1 / 3)
}
return `rgba(${r * 255},${g * 255},${b * 255},${a})`
}
function colorToRgb(val) {
if (is.rgb(val)) return rgbToRgba(val)
if (is.hex(val)) return hexToRgba(val)
if (is.hsl(val)) return hslToRgba(val)
}
// Units
function getUnit(val) {
const split = /([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(
val
)
if (split) return split[2]
}
function getTransformUnit(propName) {
if (stringContains(propName, 'translate') || propName === 'perspective')
return 'px'
if (stringContains(propName, 'rotate') || stringContains(propName, 'skew'))
return 'deg'
}
// Values
function minMaxValue(val, min, max) {
return Math.min(Math.max(val, min), max)
}
function getFunctionValue(val, animatable) {
if (!is.fnc(val)) return val
return val(animatable.target, animatable.id, animatable.total)
}
function getCSSValue(el, prop) {
if (prop in el.style) {
return getComputedStyle(el).getPropertyValue(stringToHyphens(prop)) || '0'
}
}
function getAnimationType(el, prop) {
if (is.dom(el) && arrayContains(validTransforms, prop)) return 'transform'
if (is.dom(el) && (el.getAttribute(prop) || (is.svg(el) && el[prop])))
return 'attribute'
if (is.dom(el) && (prop !== 'transform' && getCSSValue(el, prop)))
return 'css'
if (el[prop] != null) return 'object'
}
function getTransformValue(el, propName) {
const defaultUnit = getTransformUnit(propName)
const defaultVal = stringContains(propName, 'scale') ? 1 : 0 + defaultUnit
const str = el.style.transform
if (!str) return defaultVal
let match = []
let props = []
let values = []
const rgx = /(\w+)\((.+?)\)/g
while ((match = rgx.exec(str))) {
props.push(match[1])
values.push(match[2])
}
const value = filterArray(values, (val, i) => props[i] === propName)
return value.length ? value[0] : defaultVal
}
function getOriginalTargetValue(target, propName) {
switch (getAnimationType(target, propName)) {
case 'transform':
return getTransformValue(target, propName)
case 'css':
return getCSSValue(target, propName)
case 'attribute':
return target.getAttribute(propName)
}
return target[propName] || 0
}
function getRelativeValue(to, from) {
const operator = /^(\*=|\+=|-=)/.exec(to)
if (!operator) return to
const u = getUnit(to) || 0
const x = parseFloat(from)
const y = parseFloat(to.replace(operator[0], ''))
switch (operator[0][0]) {
case '+':
return x + y + u
case '-':
return x - y + u
case '*':
return x * y + u
}
}
function validateValue(val, unit) {
if (is.col(val)) return colorToRgb(val)
const originalUnit = getUnit(val)
const unitLess = originalUnit
? val.substr(0, val.length - originalUnit.length)
: val
return unit && !/\s/g.test(val) ? unitLess + unit : unitLess
}
// getTotalLength() equivalent for circle, rect, polyline, polygon and line shapes.
// adapted from https://gist.github.com/SebLambla/3e0550c496c236709744
function getDistance(p1, p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2))
}
function getCircleLength(el) {
return 2 * Math.PI * el.getAttribute('r')
}
function getRectLength(el) {
return el.getAttribute('width') * 2 + el.getAttribute('height') * 2
}
function getLineLength(el) {
return getDistance(
{ x: el.getAttribute('x1'), y: el.getAttribute('y1') },
{ x: el.getAttribute('x2'), y: el.getAttribute('y2') }
)
}
function getPolylineLength(el) {
const points = el.points
let totalLength = 0
let previousPos
for (let i = 0; i < points.numberOfItems; i++) {
const currentPos = points.getItem(i)
if (i > 0) totalLength += getDistance(previousPos, currentPos)
previousPos = currentPos
}
return totalLength
}
function getPolygonLength(el) {
const points = el.points
return (
getPolylineLength(el) +
getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0))
)
}
// Path animation
function getTotalLength(el) {
if (el.getTotalLength) return el.getTotalLength()
switch (el.tagName.toLowerCase()) {
case 'circle':
return getCircleLength(el)
case 'rect':
return getRectLength(el)
case 'line':
return getLineLength(el)
case 'polyline':
return getPolylineLength(el)
case 'polygon':
return getPolygonLength(el)
}
}
function setDashoffset(el) {
const pathLength = getTotalLength(el)
el.setAttribute('stroke-dasharray', pathLength)
return pathLength
}
// Motion path
function getPath(path, percent) {
const el = is.str(path) ? selectString(path)[0] : path
const p = percent || 100
return function(prop) {
return {
el: el,
property: prop,
totalLength: getTotalLength(el) * (p / 100),
}
}
}
function getPathProgress(path, progress) {
function point(offset = 0) {
const l = progress + offset >= 1 ? progress + offset : 0
return path.el.getPointAtLength(l)
}
const p = point()
const p0 = point(-1)
const p1 = point(+1)
switch (path.property) {
case 'x':
return p.x
case 'y':
return p.y
case 'angle':
return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI
}
}
// Decompose value
function decomposeValue(val, unit) {
const rgx = /-?\d*\.?\d+/g
const value = validateValue(is.pth(val) ? val.totalLength : val, unit) + ''
return {
original: value,
numbers: value.match(rgx) ? value.match(rgx).map(Number) : [0],
strings: is.str(val) || unit ? value.split(rgx) : [],
}
}
// Animatables
function parseElements(elements) {
const elementsArray = elements
? flattenArray(
is.arr(elements) ? elements.map(toArray) : toArray(elements)
)
: []
return filterArray(
elementsArray,
(item, pos, self) => self.indexOf(item) === pos
)
}
function getAnimatables(elements) {
const parsed = parseElements(elements)
return parsed.map((t, i) => {
return { target: t, id: i, total: parsed.length }
})
}
// Properties
function normalizePropertyTweens(prop, tweenSettings) {
let settings = cloneObject(tweenSettings)
if (is.arr(prop)) {
const l = prop.length
const isFromTo = l === 2 && !is.obj(prop[0])
if (!isFromTo) {
// Duration divided by the number of tweens
if (!is.fnc(tweenSettings.duration))
settings.duration = tweenSettings.duration / l
} else {
// Transform [from, to] values shorthand to a valid tween value
prop = { value: prop }
}
}
return toArray(prop)
.map((v, i) => {
// Default delay value should be applied only on the first tween
const delay = !i ? tweenSettings.delay : 0
// Use path object as a tween value
let obj = is.obj(v) && !is.pth(v) ? v : { value: v }
// Set default delay value
if (is.und(obj.delay)) obj.delay = delay
return obj
})
.map(k => mergeObjects(k, settings))
}
function getProperties(instanceSettings, tweenSettings, params) {
let properties = []
const settings = mergeObjects(instanceSettings, tweenSettings)
for (let p in params) {
if (!settings.hasOwnProperty(p) && p !== 'elements') {
properties.push({
name: p,
offset: settings['offset'],
tweens: normalizePropertyTweens(params[p], tweenSettings),
})
}
}
return properties
}
// Tweens
function normalizeTweenValues(tween, animatable) {
let t = {}
for (let p in tween) {
let value = getFunctionValue(tween[p], animatable)
if (is.arr(value)) {
value = value.map(v => getFunctionValue(v, animatable))
if (value.length === 1) value = value[0]
}
t[p] = value
}
t.duration = parseFloat(t.duration)
t.delay = parseFloat(t.delay)
return t
}
function normalizeEasing(val) {
return is.arr(val) ? bezier.apply(this, val) : easings[val]
}
function normalizeTweens(prop, animatable) {
let previousTween
return prop.tweens.map(t => {
let tween = normalizeTweenValues(t, animatable)
const tweenValue = tween.value
const originalValue = getOriginalTargetValue(animatable.target, prop.name)
const previousValue = previousTween
? previousTween.to.original
: originalValue
const from = is.arr(tweenValue) ? tweenValue[0] : previousValue
const to = getRelativeValue(
is.arr(tweenValue) ? tweenValue[1] : tweenValue,
from
)
const unit = getUnit(to) || getUnit(from) || getUnit(originalValue)
tween.from = decomposeValue(from, unit)
tween.to = decomposeValue(to, unit)
tween.start = previousTween ? previousTween.end : prop.offset
tween.end = tween.start + tween.delay + tween.duration
tween.easing = normalizeEasing(tween.easing)
tween.elasticity = (1000 - minMaxValue(tween.elasticity, 1, 999)) / 1000
tween.isPath = is.pth(tweenValue)
tween.isColor = is.col(tween.from.original)
if (tween.isColor) tween.round = 1
previousTween = tween
return tween
})
}
// Tween progress
const setTweenProgress = {
css: (t, p, v) => (t.style[p] = v),
attribute: (t, p, v) => t.setAttribute(p, v),
object: (t, p, v) => (t[p] = v),
transform: (t, p, v, transforms, id) => {
if (!transforms[id]) transforms[id] = []
transforms[id].push(`${p}(${v})`)
},
}
// Animations
function createAnimation(animatable, prop) {
const animType = getAnimationType(animatable.target, prop.name)
if (animType) {
const tweens = normalizeTweens(prop, animatable)
return {
type: animType,
property: prop.name,
animatable: animatable,
tweens: tweens,
duration: tweens[tweens.length - 1].end,
delay: tweens[0].delay,
}
}
}
function getAnimations(animatables, properties) {
return filterArray(
flattenArray(
animatables.map(animatable => {
return properties.map(prop => {
return createAnimation(animatable, prop)
})
})
),
a => !is.und(a)
)
}
// Create Instance
function getInstanceTimings(
type,
animations,
instanceSettings,
tweenSettings
) {
const isDelay = type === 'delay'
if (animations.length) {
return (isDelay ? Math.min : Math.max).apply(
Math,
animations.map(anim => anim[type])
)
} else {
return isDelay
? tweenSettings.delay
: instanceSettings.offset + tweenSettings.delay + tweenSettings.duration
}
}
function createNewInstance(params) {
const instanceSettings = replaceObjectProps(defaultInstanceSettings, params)
const tweenSettings = replaceObjectProps(defaultTweenSettings, params)
const animatables = getAnimatables(params.elements)
const properties = getProperties(instanceSettings, tweenSettings, params)
const animations = getAnimations(animatables, properties)
return mergeObjects(instanceSettings, {
children: [],
animatables: animatables,
animations: animations,
duration: getInstanceTimings(
'duration',
animations,
instanceSettings,
tweenSettings
),
delay: getInstanceTimings(
'delay',
animations,
instanceSettings,
tweenSettings
),
})
}
// Core
let activeInstances = []
let raf = 0
const engine = (() => {
function play() {
raf = requestAnimationFrame(step)
}
function step(t) {
const activeLength = activeInstances.length
if (activeLength) {
let i = 0
while (i < activeLength) {
if (activeInstances[i]) activeInstances[i].tick(t)
i++
}
play()
} else {
cancelAnimationFrame(raf)
raf = 0
}
}
return play
})()
// Public Instance
function anime(params = {}) {
let now,
startTime,
lastTime = 0
let resolve = null
function makePromise() {
return window.Promise && new Promise(_resolve => (resolve = _resolve))
}
let promise = makePromise()
let instance = createNewInstance(params)
function toggleInstanceDirection() {
instance.reversed = !instance.reversed
}
function adjustTime(time) {
return instance.reversed ? instance.duration - time : time
}
function syncInstanceChildren(time) {
const children = instance.children
const childrenLength = children.length
if (time >= instance.currentTime) {
for (let i = 0; i < childrenLength; i++) children[i].seek(time)
} else {
for (let i = childrenLength; i--; ) children[i].seek(time)
}
}
function setAnimationsProgress(insTime) {
let i = 0
let transforms = {}
const animations = instance.animations
const animationsLength = animations.length
while (i < animationsLength) {
const anim = animations[i]
const animatable = anim.animatable
const tweens = anim.tweens
const tweenLength = tweens.length - 1
let tween = tweens[tweenLength]
// Only check for keyframes if there is more than one tween
if (tweenLength)
tween = filterArray(tweens, t => insTime < t.end)[0] || tween
const elapsed =
minMaxValue(insTime - tween.start - tween.delay, 0, tween.duration) /
tween.duration
const eased = isNaN(elapsed)
? 1
: tween.easing(elapsed, tween.elasticity)
const strings = tween.to.strings
const round = tween.round
let numbers = []
let progress
const toNumbersLength = tween.to.numbers.length
for (let n = 0; n < toNumbersLength; n++) {
let value
const toNumber = tween.to.numbers[n]
const fromNumber = tween.from.numbers[n]
if (!tween.isPath) {
value = fromNumber + eased * (toNumber - fromNumber)
} else {
value = getPathProgress(tween.value, eased * toNumber)
}
if (round) {
if (!(tween.isColor && n > 2)) {
value = Math.round(value * round) / round
}
}
numbers.push(value)
}
// Manual Array.reduce for better performances
const stringsLength = strings.length
if (!stringsLength) {
progress = numbers[0]
} else {
progress = strings[0]
for (let s = 0; s < stringsLength; s++) {
const a = strings[s]
const b = strings[s + 1]
const n = numbers[s]
if (!isNaN(n)) {
if (!b) {
progress += n + ' '
} else {
progress += n + b
}
}
}
}
setTweenProgress[anim.type](
animatable.target,
anim.property,
progress,
transforms,
animatable.id
)
anim.currentValue = progress
i++
}
const transformsLength = Object.keys(transforms).length
if (transformsLength) {
for (let id = 0; id < transformsLength; id++) {
if (!transformString) {
const t = 'transform'
transformString = getCSSValue(document.body, t) ? t : `-webkit-${t}`
}
instance.animatables[id].target.style[transformString] = transforms[
id
].join(' ')
}
}
instance.currentTime = insTime
instance.progress = insTime / instance.duration * 100
}
function setCallback(cb) {
const {
completed,
progress,
duration,
remaining,
reversed,
currentTime,
began,
paused,
play,
pause,
restart,
reverse,
reset,
} = instance
const controller = {
start: play,
stop: pause,
restart,
reverse,
reset,
}
const finalProps = {
completed,
progress,
duration,
remaining,
reversed,
currentTime,
began,
paused,
controller,
}
if (instance[cb]) instance[cb](finalProps)
}
function countIteration() {
if (instance.remaining && instance.remaining !== true) {
instance.remaining--
}
}
function setInstanceProgress(engineTime) {
const insDuration = instance.duration
const insOffset = instance.offset
const insStart = insOffset + instance.delay
const insCurrentTime = instance.currentTime
const insReversed = instance.reversed
const insTime = adjustTime(engineTime)
if (instance.children.length) syncInstanceChildren(insTime)
if (insTime >= insStart || !insDuration) {
if (!instance.began) {
instance.began = true
setCallback('begin')
}
setCallback('run')
}
if (insTime > insOffset && insTime < insDuration) {
setAnimationsProgress(insTime)
} else {
if (insTime <= insOffset && insCurrentTime !== 0) {
setAnimationsProgress(0)
if (insReversed) countIteration()
}
if (
(insTime >= insDuration && insCurrentTime !== insDuration) ||
!insDuration
) {
setAnimationsProgress(insDuration)
if (!insReversed) countIteration()
}
}
setCallback('update')
if (engineTime >= insDuration) {
if (instance.remaining) {
startTime = now
if (instance.direction === 'alternate') toggleInstanceDirection()
} else {
instance.pause()
if (!instance.completed) {
instance.completed = true
setCallback('complete')
if ('Promise' in window) {
resolve()
promise = makePromise()
}
}
}
lastTime = 0
}
}
instance.reset = function() {
const direction = instance.direction
const loops = instance.loop
instance.currentTime = 0
instance.progress = 0
instance.paused = true
instance.began = false
instance.completed = false
instance.reversed = direction === 'reverse'
instance.remaining = direction === 'alternate' && loops === 1 ? 2 : loops
setAnimationsProgress(0)
for (let i = instance.children.length; i--; ) {
instance.children[i].reset()
}
}
instance.tick = function(t) {
now = t
if (!startTime) startTime = now
const engineTime = (lastTime + now - startTime) * instance.speed || 1
setInstanceProgress(engineTime)
}
instance.seek = function(time) {
setInstanceProgress(adjustTime(time))
}
instance.pause = function() {
const i = activeInstances.indexOf(instance)
if (i > -1) activeInstances.splice(i, 1)
instance.paused = true
}
instance.play = function() {
if (!instance.paused) return
instance.paused = false
startTime = 0
lastTime = adjustTime(instance.currentTime)
activeInstances.push(instance)
if (!raf) engine()
}
instance.reverse = function() {
toggleInstanceDirection()
startTime = 0
lastTime = adjustTime(instance.currentTime)
}
instance.restart = function() {
instance.pause()
instance.reset()
instance.play()
}
instance.finished = promise
instance.reset()
if (instance.autoplay) instance.play()
return instance
}
// Remove elements from animation
function removeelements(elements) {
const elementsArray = parseElements(elements)
for (let i = activeInstances.length; i--; ) {
const instance = activeInstances[i]
const animations = instance.animations
for (let a = animations.length; a--; ) {
if (arrayContains(elementsArray, animations[a].animatable.target)) {
animations.splice(a, 1)
if (!animations.length) instance.pause()
}
}
}
}
// Timeline
function timeline(params) {
let tl = anime(params)
tl.pause()
tl.duration = 0
tl.add = function(instancesParams) {
tl.children.forEach(i => {
i.began = true
i.completed = true
})
toArray(instancesParams).forEach(instanceParams => {
let insParams = mergeObjects(
instanceParams,
replaceObjectProps(defaultTweenSettings, params || {})
)
insParams.elements = insParams.elements || params.elements
const tlDuration = tl.duration
const insOffset = insParams.offset
insParams.autoplay = false
insParams.direction = tl.direction
insParams.offset = is.und(insOffset)
? tlDuration
: getRelativeValue(insOffset, tlDuration)
tl.began = true
tl.completed = true
tl.seek(insParams.offset)
const ins = anime(insParams)
ins.began = true
ins.completed = true
if (ins.duration > tlDuration) tl.duration = ins.duration
tl.children.push(ins)
})
tl.seek(0)
tl.reset()
if (tl.autoplay) tl.restart()
return tl
}
return tl
}
anime.getValue = getOriginalTargetValue
anime.path = getPath
anime.bezier = bezier
anime.easings = easings
anime.timeline = timeline
anime.random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
return anime
})