fastlion-amis
Version:
一种MIS页面生成工具
384 lines (327 loc) • 10.5 kB
text/typescript
// 基本上都是从 sortable 那抄的,让拖拽切换有个动画,看起来更流畅。
// 用法是移动前先 animat.capture(container) 把移动前的位置信息记住
// 然后移动节点
// 然后 animate.animateAll(); 计算移动后的位置,然后马上通过 css transform 到原来的位置
// 然后开始动画到移动后的位置。
interface Rect {
top: number;
left: number;
bottom: number;
right: number;
width: number;
height: number;
}
interface AnimationState {
rect: Rect;
target: HTMLElement;
}
function userAgent(pattern: RegExp): any {
if (typeof window !== 'undefined' && window.navigator) {
return !!(/*@__PURE__*/ navigator.userAgent.match(pattern));
}
}
const IE11OrLess = userAgent(
/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i
);
// const Edge = userAgent(/Edge/i);
// const FireFox = userAgent(/firefox/i);
// const Safari =
// userAgent(/safari/i) && !userAgent(/chrome/i) && !userAgent(/android/i);
// const IOS = userAgent(/iP(ad|od|hone)/i);
// const ChromeForAndroid = userAgent(/chrome/i) && userAgent(/android/i);
const AnimationDurtation = 150;
const AnimationEasing = 'cubic-bezier(1, 0, 0, 1)';
export class AnimationManager {
animating: boolean = false;
animationCallbackId: any;
states: Array<AnimationState> = [];
capture(el: HTMLElement) {
// 清空,重复调用,旧的不管了
this.states = [];
let children: Array<HTMLElement> = [].slice.call(el.children);
children.forEach(child => {
// 如果是 ghost
if (child.classList.contains('is-ghost')) {
return;
}
const rect = getRect(child)!;
// 通常是隐藏节点
if (!rect.width) {
return;
}
let fromRect = {...rect};
const state: AnimationState = {
target: child,
rect
};
// 还在动画中
if ((child as any).thisAnimationDuration) {
let childMatrix = matrix(child);
if (childMatrix) {
fromRect.top -= childMatrix.f;
fromRect.left -= childMatrix.e;
}
}
(child as any).fromRect = fromRect;
this.states.push(state);
});
}
animateAll(callback?: () => void) {
this.animating = false;
let animationTime = 0;
this.states.forEach(state => {
let time = 0,
target = state.target,
fromRect = (target as any).fromRect,
toRect = {
...getRect(target)!
},
prevFromRect = (target as any).prevFromRect,
prevToRect = (target as any).prevToRect,
animatingRect = state.rect,
targetMatrix = matrix(target);
if (targetMatrix) {
// Compensate for current animation
toRect.top -= targetMatrix.f;
toRect.left -= targetMatrix.e;
}
(target as any).toRect = toRect;
if ((target as any).thisAnimationDuration) {
// Could also check if animatingRect is between fromRect and toRect
if (
isRectEqual(prevFromRect, toRect) &&
!isRectEqual(fromRect, toRect) &&
// Make sure animatingRect is on line between toRect & fromRect
(animatingRect.top - toRect.top) /
(animatingRect.left - toRect.left) ===
(fromRect.top - toRect.top) / (fromRect.left - toRect.left)
) {
// If returning to same place as started from animation and on same axis
time = calculateRealTime(animatingRect, prevFromRect, prevToRect);
}
}
// if fromRect != toRect: animate
if (!isRectEqual(toRect, fromRect)) {
(target as any).prevFromRect = fromRect;
(target as any).prevToRect = toRect;
if (!time) {
time = AnimationDurtation;
}
this.animate(target, animatingRect, toRect, time);
}
if (time) {
this.animating = true;
animationTime = Math.max(animationTime, time);
clearTimeout((target as any).animationResetTimer);
(target as any).animationResetTimer = setTimeout(function () {
(target as any).animationTime = 0;
(target as any).prevFromRect = null;
(target as any).fromRect = null;
(target as any).prevToRect = null;
(target as any).thisAnimationDuration = null;
}, time);
(target as any).thisAnimationDuration = time;
}
});
clearTimeout(this.animationCallbackId);
if (!this.animating) {
if (typeof callback === 'function') callback();
} else {
this.animationCallbackId = setTimeout(() => {
this.animating = false;
if (typeof callback === 'function') callback();
}, animationTime);
}
this.states = [];
}
animate(
target: HTMLElement,
currentRect: Rect,
toRect: Rect,
duration: number
) {
if (duration) {
let affectDisplay = false;
css(target, 'transition', '');
css(target, 'transform', '');
let translateX = currentRect.left - toRect.left,
translateY = currentRect.top - toRect.top;
(target as any).animatingX = !!translateX;
(target as any).animatingY = !!translateY;
css(
target,
'transform',
'translate3d(' + translateX + 'px,' + translateY + 'px,0)'
);
if (css(target, 'display') === 'inline') {
affectDisplay = true;
css(target, 'display', 'inline-block');
}
target.offsetWidth; // repaint
css(
target,
'transition',
'transform ' +
duration +
'ms' +
(AnimationEasing ? ' ' + AnimationEasing : '')
);
css(target, 'transform', 'translate3d(0,0,0)');
typeof (target as any).animated === 'number' &&
clearTimeout((target as any).animated);
(target as any).animated = setTimeout(function () {
css(target, 'transition', '');
css(target, 'transform', '');
affectDisplay && css(target, 'display', '');
(target as any).animated = false;
(target as any).animatingX = false;
(target as any).animatingY = false;
}, duration);
}
}
}
function matrix(el: HTMLElement) {
let appliedTransforms = '';
if (typeof el === 'string') {
appliedTransforms = el;
} else {
let transform = css(el, 'transform');
if (transform && transform !== 'none') {
appliedTransforms = transform + ' ' + appliedTransforms;
}
}
const matrixFn =
window.DOMMatrix ||
window.WebKitCSSMatrix ||
(window as any).CSSMatrix ||
(window as any).MSCSSMatrix;
/*jshint -W056 */
return matrixFn && new matrixFn(appliedTransforms);
}
function css(el: HTMLElement, prop: string, val?: any) {
let style = el && el.style;
if (style) {
if (val === void 0) {
if (document.defaultView && document.defaultView.getComputedStyle) {
val = document.defaultView.getComputedStyle(el, '');
} else if ((el as any).currentStyle) {
val = (el as any).currentStyle;
}
return prop === void 0 ? val : val[prop];
} else {
if (!(prop in style) && prop.indexOf('webkit') === -1) {
prop = '-webkit-' + prop;
}
(style as any)[prop] = val + (typeof val === 'string' ? '' : 'px');
}
}
}
function isRectEqual(rect1: Rect, rect2: Rect) {
return (
Math.round(rect1.top) === Math.round(rect2.top) &&
Math.round(rect1.left) === Math.round(rect2.left) &&
Math.round(rect1.height) === Math.round(rect2.height) &&
Math.round(rect1.width) === Math.round(rect2.width)
);
}
function calculateRealTime(animatingRect: Rect, fromRect: Rect, toRect: Rect) {
return (
(Math.sqrt(
Math.pow(fromRect.top - animatingRect.top, 2) +
Math.pow(fromRect.left - animatingRect.left, 2)
) /
Math.sqrt(
Math.pow(fromRect.top - toRect.top, 2) +
Math.pow(fromRect.left - toRect.left, 2)
)) *
AnimationDurtation
);
}
function getWindowScrollingElement() {
let scrollingElement = document.scrollingElement;
if (scrollingElement) {
return scrollingElement;
} else {
return document.documentElement;
}
}
function getRect(
el: HTMLElement,
relativeToContainingBlock?: boolean,
relativeToNonStaticParent?: boolean,
undoScale?: boolean,
container?: HTMLElement
) {
if (!el.getBoundingClientRect && (el as any) !== window) return;
let elRect, top, left, bottom, right, height, width;
if ((el as any) !== window && el !== getWindowScrollingElement()) {
elRect = el.getBoundingClientRect();
top = elRect.top;
left = elRect.left;
bottom = elRect.bottom;
right = elRect.right;
height = elRect.height;
width = elRect.width;
} else {
top = 0;
left = 0;
bottom = window.innerHeight;
right = window.innerWidth;
height = window.innerHeight;
width = window.innerWidth;
}
if (
(relativeToContainingBlock || relativeToNonStaticParent) &&
(el as any) !== window
) {
// Adjust for translate()
container = container || (el.parentNode as HTMLElement);
// solves #1123 (see: https://stackoverflow.com/a/37953806/6088312)
// Not needed on <= IE11
if (!IE11OrLess) {
do {
if (
container &&
container.getBoundingClientRect &&
(css(container, 'transform') !== 'none' ||
(relativeToNonStaticParent &&
css(container, 'position') !== 'static'))
) {
let containerRect = container.getBoundingClientRect();
// Set relative to edges of padding box of container
top -=
containerRect.top + parseInt(css(container, 'border-top-width'));
left -=
containerRect.left + parseInt(css(container, 'border-left-width'));
bottom = top + (elRect as any).height;
right = left + (elRect as any).width;
break;
}
/* jshint boss:true */
} while ((container = container.parentNode as HTMLElement));
}
}
if (undoScale && (el as any) !== window) {
// Adjust for scale()
let elMatrix = matrix(container || el),
scaleX = elMatrix && elMatrix.a,
scaleY = elMatrix && elMatrix.d;
if (elMatrix) {
top /= scaleY;
left /= scaleX;
width /= scaleX;
height /= scaleY;
bottom = top + height;
right = left + width;
}
}
return {
top: top,
left: left,
bottom: bottom,
right: right,
width: width,
height: height
};
}
export default new AnimationManager();