hae
Version:
Mobile web UI based on Vux
423 lines (383 loc) • 9.38 kB
JavaScript
/**
* http://ionicframework.com/docs/api/directive/ionSpinner/
*/
import './requestAnimationFrame'
var TRANSLATE32 = 'translate(32,32)'
var STROKE_OPACITY = 'stroke-opacity'
var ROUND = 'round'
var INDEFINITE = 'indefinite'
var DURATION = '750ms'
var NONE = 'none'
var SHORTCUTS = {
a: 'animate',
an: 'attributeName',
at: 'animateTransform',
c: 'circle',
da: 'stroke-dasharray',
os: 'stroke-dashoffset',
f: 'fill',
lc: 'stroke-linecap',
rc: 'repeatCount',
sw: 'stroke-width',
t: 'transform',
v: 'values'
}
var SPIN_ANIMATION = {
v: '0,32,32;360,32,32',
an: 'transform',
type: 'rotate',
rc: INDEFINITE,
dur: DURATION
}
function createSvgElement (tagName, data, parent, spinnerName, size) {
var ele = document.createElement(SHORTCUTS[tagName] || tagName)
var k, x, y
for (k in data) {
if (Object.prototype.toString.call(data[k]) === '[object Array]') {
for (x = 0; x < data[k].length; x++) {
if (data[k][x].fn) {
for (y = 0; y < data[k][x].t; y++) {
createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName)
}
} else {
createSvgElement(k, data[k][x], ele, spinnerName)
}
}
} else {
setSvgAttribute(ele, k, data[k])
}
}
if (size && size !== '28px') {
setSvgAttribute(ele, 'style', `width: ${size}; height: ${size}`)
}
parent.appendChild(ele)
}
function setSvgAttribute (ele, k, v) {
ele.setAttribute(SHORTCUTS[k] || k, v)
}
function animationValues (strValues, i) {
var values = strValues.split(';')
var back = values.slice(i)
var front = values.slice(0, values.length - back.length)
values = back.concat(front).reverse()
return values.join(';') + ';' + values[0]
}
var IOS_SPINNER = {
sw: 4,
lc: ROUND,
line: [{
fn (i, spinnerName) {
return {
y1: spinnerName === 'ios' ? 17 : 12,
y2: spinnerName === 'ios' ? 29 : 20,
t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
a: [{
fn () {
return {
an: STROKE_OPACITY,
dur: DURATION,
v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 12
}]
}
var spinners = {
android: {
c: [{
sw: 6,
da: 128,
os: 82,
r: 26,
cx: 32,
cy: 32,
f: NONE
}]
},
ios: IOS_SPINNER,
'ios-small': IOS_SPINNER,
bubbles: {
sw: 0,
c: [{
fn (i) {
return {
cx: 24 * Math.cos(2 * Math.PI * i / 8),
cy: 24 * Math.sin(2 * Math.PI * i / 8),
t: TRANSLATE32,
a: [{
fn () {
return {
an: 'r',
dur: DURATION,
v: animationValues('1;2;3;4;5;6;7;8', i),
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 8
}]
},
circles: {
c: [{
fn (i) {
return {
r: 5,
cx: 24 * Math.cos(2 * Math.PI * i / 8),
cy: 24 * Math.sin(2 * Math.PI * i / 8),
t: TRANSLATE32,
sw: 0,
a: [{
fn () {
return {
an: 'fill-opacity',
dur: DURATION,
v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 8
}]
},
crescent: {
c: [{
sw: 4,
da: 128,
os: 82,
r: 26,
cx: 32,
cy: 32,
f: NONE,
at: [SPIN_ANIMATION]
}]
},
dots: {
c: [{
fn (i) {
return {
cx: 16 + (16 * i),
cy: 32,
sw: 0,
a: [{
fn () {
return {
an: 'fill-opacity',
dur: DURATION,
v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
rc: INDEFINITE
}
},
t: 1
}, {
fn () {
return {
an: 'r',
dur: DURATION,
v: animationValues('4;5;6;5;4;3;3', i),
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 3
}]
},
lines: {
sw: 7,
lc: ROUND,
line: [{
fn (i) {
return {
x1: 10 + (i * 14),
x2: 10 + (i * 14),
a: [{
fn () {
return {
an: 'y1',
dur: DURATION,
v: animationValues('16;18;28;18;16', i),
rc: INDEFINITE
}
},
t: 1
}, {
fn () {
return {
an: 'y2',
dur: DURATION,
v: animationValues('48;44;36;46;48', i),
rc: INDEFINITE
}
},
t: 1
}, {
fn () {
return {
an: STROKE_OPACITY,
dur: DURATION,
v: animationValues('1;.8;.5;.4;1', i),
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 4
}]
},
ripple: {
f: NONE,
'fill-rule': 'evenodd',
sw: 3,
circle: [{
fn (i) {
return {
cx: 32,
cy: 32,
a: [{
fn () {
return {
an: 'r',
begin: (i * -1) + 's',
dur: '2s',
v: '0;24',
keyTimes: '0;1',
keySplines: '0.1,0.2,0.3,1',
calcMode: 'spline',
rc: INDEFINITE
}
},
t: 1
}, {
fn () {
return {
an: STROKE_OPACITY,
begin: (i * -1) + 's',
dur: '2s',
v: '.2;1;.2;0',
rc: INDEFINITE
}
},
t: 1
}]
}
},
t: 2
}]
},
spiral: {
defs: [{
linearGradient: [{
id: 'sGD',
gradientUnits: 'userSpaceOnUse',
x1: 55,
y1: 46,
x2: 2,
y2: 46,
stop: [{
offset: 0.1,
class: 'stop1'
}, {
offset: 1,
class: 'stop2'
}]
}]
}],
g: [{
sw: 4,
lc: ROUND,
f: NONE,
path: [{
stroke: 'url(#sGD)',
d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
}, {
d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
}],
at: [SPIN_ANIMATION]
}]
}
}
var animations = {
android (ele) {
var self = this
this.stop = false
var rIndex = 0
var rotateCircle = 0
var startTime
var svgEle = ele.querySelector('g')
var circleEle = ele.querySelector('circle')
function run () {
if (self.stop) return
var v = easeInOutCubic(Date.now() - startTime, 650)
var scaleX = 1
var translateX = 0
var dasharray = (188 - (58 * v))
var dashoffset = (182 - (182 * v))
if (rIndex % 2) {
scaleX = -1
translateX = -64
dasharray = (128 - (-58 * v))
dashoffset = (182 * v)
}
var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex]
setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128))
setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0))
setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)')
rotateCircle += 4.1
if (rotateCircle > 359) rotateCircle = 0
setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)')
if (v >= 1) {
rIndex++
if (rIndex > 7) rIndex = 0
startTime = Date.now()
}
window.requestAnimationFrame(run)
}
return function () {
startTime = Date.now()
run()
return self
}
}
}
function easeInOutCubic (t, c) {
t /= c / 2
if (t < 1) return 1 / 2 * t * t * t
t -= 2
return 1 / 2 * (t * t * t + 2)
}
export default function (el, icon, size) {
var spinnerName, anim // eslint-disable-line
spinnerName = icon
var container = document.createElement('div')
createSvgElement('svg', {
viewBox: '0 0 64 64',
g: [spinners[spinnerName]]
}, container, spinnerName, size)
// Specifically for animations to work,
// Android 4.3 and below requires the element to be
// added as an html string, rather than dynmically
// building up the svg element and appending it.
el.innerHTML = container.innerHTML
start()
function start () {
if (animations[spinnerName]) {
anim = animations[spinnerName](el)()
}
}
return el
}