UNPKG

avalon2

Version:

an elegant efficient express mvvm framework

317 lines (294 loc) 9.36 kB
import { avalon, window, Cache } from '../seed/core' import { cssDiff } from '../directives/css' import { css3, animation, transition, animationEndEvent, transitionEndEvent } from './detect' var effectDir = avalon.directive('effect', { priority: 5, diff: function (effect) { var vdom = this.node if (typeof effect === 'string') { this.value = effect = { is: effect } avalon.warn('ms-effect的指令值不再支持字符串,必须是一个对象') } this.value = vdom.effect = effect var ok = cssDiff.call(this, effect, this.oldValue) var me = this if (ok) { setTimeout(function () { vdom.animating = true effectDir.update.call(me, vdom, vdom.effect) }) vdom.animating = false return true } return false }, update: function (vdom, change, opts) { var dom = vdom.dom if (dom && dom.nodeType === 1) { //要求配置对象必须指定is属性,action必须是布尔或enter,leave,move var option = change || opts var is = option.is var globalOption = avalon.effects[is] if (!globalOption) {//如果没有定义特效 avalon.warn(is + ' effect is undefined') return } var finalOption = {} var action = actionMaps[option.action] if (typeof Effect.prototype[action] !== 'function') { avalon.warn('action is undefined') return } //必须预定义特效 var effect = new avalon.Effect(dom) avalon.mix(finalOption, globalOption, option, { action }) if (finalOption.queue) { animationQueue.push(function () { effect[action](finalOption) }) callNextAnimation() } else { effect[action](finalOption) } return true } } }) let move = 'move' let leave = 'leave' let enter = 'enter' var actionMaps = { 'true': enter, 'false': leave, enter, leave, move, 'undefined': enter } var animationQueue = [] export function callNextAnimation() { var fn = animationQueue[0] if (fn) { fn() } } avalon.effects = {} avalon.effect = function (name, opts) { var definition = avalon.effects[name] = (opts || {}) if (css3 && definition.css !== false) { patchObject(definition, 'enterClass', name + '-enter') patchObject(definition, 'enterActiveClass', definition.enterClass + '-active') patchObject(definition, 'leaveClass', name + '-leave') patchObject(definition, 'leaveActiveClass', definition.leaveClass + '-active') } return definition } function patchObject(obj, name, value) { if (!obj[name]) { obj[name] = value } } var Effect = function (dom) { this.dom = dom } avalon.Effect = Effect Effect.prototype = { enter: createAction('Enter'), leave: createAction('Leave'), move: createAction('Move') } function execHooks(options, name, el) { var fns = [].concat(options[name]) for (var i = 0, fn; fn = fns[i++];) { if (typeof fn === 'function') { fn(el) } } } var staggerCache = new Cache(128) function createAction(action) { var lower = action.toLowerCase() return function (option) { var dom = this.dom var elem = avalon(dom) //处理与ms-for指令相关的stagger //========BEGIN===== var staggerTime = isFinite(option.stagger) ? option.stagger * 1000 : 0 if (staggerTime) { if (option.staggerKey) { var stagger = staggerCache.get(option.staggerKey) || staggerCache.put(option.staggerKey, { count: 0, items: 0 }) stagger.count++ stagger.items++ } } var staggerIndex = stagger && stagger.count || 0 //=======END========== var stopAnimationID var animationDone = function (e) { var isOk = e !== false if (--dom.__ms_effect_ === 0) { avalon.unbind(dom, transitionEndEvent) avalon.unbind(dom, animationEndEvent) } clearTimeout(stopAnimationID) var dirWord = isOk ? 'Done' : 'Abort' execHooks(option, 'on' + action + dirWord, dom) if (stagger) { if (--stagger.items === 0) { stagger.count = 0 } } if (option.queue) { animationQueue.shift() callNextAnimation() } } //执行开始前的钩子 execHooks(option, 'onBefore' + action, dom) if (option[lower]) { //使用JS方式执行动画 option[lower](dom, function (ok) { animationDone(ok !== false) }) } else if (css3) { //使用CSS3方式执行动画 elem.addClass(option[lower + 'Class']) elem.removeClass(getNeedRemoved(option, lower)) if (!dom.__ms_effect_) { //绑定动画结束事件 elem.bind(transitionEndEvent, animationDone) elem.bind(animationEndEvent, animationDone) dom.__ms_effect_ = 1 } else { dom.__ms_effect_++ } setTimeout(function () { //用xxx-active代替xxx类名的方式 触发CSS3动画 var time = avalon.root.offsetWidth === NaN elem.addClass(option[lower + 'ActiveClass']) //计算动画时长 time = getAnimationTime(dom) if (!time === 0) { //立即结束动画 animationDone(false) } else if (!staggerTime) { //如果动画超出时长还没有调用结束事件,这可能是元素被移除了 //如果强制结束动画 stopAnimationID = setTimeout(function () { animationDone(false) }, time + 32) } }, 17 + staggerTime * staggerIndex)// = 1000/60 } } } avalon.applyEffect = function (dom, vdom, opts) { var cb = opts.cb var curEffect = vdom.effect if (curEffect && dom && dom.nodeType === 1) { var hook = opts.hook var old = curEffect[hook] if (cb) { if (Array.isArray(old)) { old.push(cb) } else if (old) { curEffect[hook] = [old, cb] } else { curEffect[hook] = [cb] } } getAction(opts) avalon.directives.effect.update(vdom, curEffect, avalon.shadowCopy({}, opts)) } else if (cb) { cb(dom) } } /** * 获取方向 */ export function getAction(opts) { if (!opts.action) { return opts.action = opts.hook.replace(/^on/, '').replace(/Done$/, '').toLowerCase() } } /** * 需要移除的类名 */ function getNeedRemoved(options, name) { var name = name === 'leave' ? 'enter' : 'leave' return Array(name + 'Class', name + 'ActiveClass').map(function (cls) { return options[cls] }).join(' ') } /** * 计算动画长度 */ var transitionDuration = avalon.cssName('transition-duration') var animationDuration = avalon.cssName('animation-duration') var rsecond = /\d+s$/ export function toMillisecond(str) { var ratio = rsecond.test(str) ? 1000 : 1 return parseFloat(str) * ratio } export function getAnimationTime(dom) { var computedStyles = window.getComputedStyle(dom,null) var tranDuration = computedStyles[transitionDuration] var animDuration = computedStyles[animationDuration] return toMillisecond(tranDuration) || toMillisecond(animDuration) } /** * <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="dist/avalon.js"></script> <script> avalon.effect('animate') var vm = avalon.define({ $id: 'ani', a: true }) </script> <style> .animate-enter, .animate-leave{ width:100px; height:100px; background: #29b6f6; transition:all 2s; -moz-transition: all 2s; -webkit-transition: all 2s; -o-transition:all 2s; } .animate-enter-active, .animate-leave{ width:300px; height:300px; } .animate-leave-active{ width:100px; height:100px; } </style> </head> <body> <div :controller='ani' > <p><input type='button' value='click' :click='@a =!@a'></p> <div :effect="{is:'animate',action:@a}"></div> </div> </body> </html> * */