cssAnimation.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import { copyTransform } from '../core/Transformable.js';
  2. import { createBrushScope } from './core.js';
  3. import SVGPathRebuilder from './SVGPathRebuilder.js';
  4. import PathProxy from '../core/PathProxy.js';
  5. import { getPathPrecision, getSRTTransformString } from './helper.js';
  6. import { each, extend, filter, isNumber, isString, keys } from '../core/util.js';
  7. import CompoundPath from '../graphic/CompoundPath.js';
  8. import { createCubicEasingFunc } from '../animation/cubicEasing.js';
  9. import { getClassId } from './cssClassId.js';
  10. export var EASING_MAP = {
  11. cubicIn: '0.32,0,0.67,0',
  12. cubicOut: '0.33,1,0.68,1',
  13. cubicInOut: '0.65,0,0.35,1',
  14. quadraticIn: '0.11,0,0.5,0',
  15. quadraticOut: '0.5,1,0.89,1',
  16. quadraticInOut: '0.45,0,0.55,1',
  17. quarticIn: '0.5,0,0.75,0',
  18. quarticOut: '0.25,1,0.5,1',
  19. quarticInOut: '0.76,0,0.24,1',
  20. quinticIn: '0.64,0,0.78,0',
  21. quinticOut: '0.22,1,0.36,1',
  22. quinticInOut: '0.83,0,0.17,1',
  23. sinusoidalIn: '0.12,0,0.39,0',
  24. sinusoidalOut: '0.61,1,0.88,1',
  25. sinusoidalInOut: '0.37,0,0.63,1',
  26. exponentialIn: '0.7,0,0.84,0',
  27. exponentialOut: '0.16,1,0.3,1',
  28. exponentialInOut: '0.87,0,0.13,1',
  29. circularIn: '0.55,0,1,0.45',
  30. circularOut: '0,0.55,0.45,1',
  31. circularInOut: '0.85,0,0.15,1'
  32. };
  33. var transformOriginKey = 'transform-origin';
  34. function buildPathString(el, kfShape, path) {
  35. var shape = extend({}, el.shape);
  36. extend(shape, kfShape);
  37. el.buildPath(path, shape);
  38. var svgPathBuilder = new SVGPathRebuilder();
  39. svgPathBuilder.reset(getPathPrecision(el));
  40. path.rebuildPath(svgPathBuilder, 1);
  41. svgPathBuilder.generateStr();
  42. return svgPathBuilder.getStr();
  43. }
  44. function setTransformOrigin(target, transform) {
  45. var originX = transform.originX, originY = transform.originY;
  46. if (originX || originY) {
  47. target[transformOriginKey] = originX + "px " + originY + "px";
  48. }
  49. }
  50. export var ANIMATE_STYLE_MAP = {
  51. fill: 'fill',
  52. opacity: 'opacity',
  53. lineWidth: 'stroke-width',
  54. lineDashOffset: 'stroke-dashoffset'
  55. };
  56. function addAnimation(cssAnim, scope) {
  57. var animationName = scope.zrId + '-ani-' + scope.cssAnimIdx++;
  58. scope.cssAnims[animationName] = cssAnim;
  59. return animationName;
  60. }
  61. function createCompoundPathCSSAnimation(el, attrs, scope) {
  62. var paths = el.shape.paths;
  63. var composedAnim = {};
  64. var cssAnimationCfg;
  65. var cssAnimationName;
  66. each(paths, function (path) {
  67. var subScope = createBrushScope(scope.zrId);
  68. subScope.animation = true;
  69. createCSSAnimation(path, {}, subScope, true);
  70. var cssAnims = subScope.cssAnims;
  71. var cssNodes = subScope.cssNodes;
  72. var animNames = keys(cssAnims);
  73. var len = animNames.length;
  74. if (!len) {
  75. return;
  76. }
  77. cssAnimationName = animNames[len - 1];
  78. var lastAnim = cssAnims[cssAnimationName];
  79. for (var percent in lastAnim) {
  80. var kf = lastAnim[percent];
  81. composedAnim[percent] = composedAnim[percent] || { d: '' };
  82. composedAnim[percent].d += kf.d || '';
  83. }
  84. for (var className in cssNodes) {
  85. var val = cssNodes[className].animation;
  86. if (val.indexOf(cssAnimationName) >= 0) {
  87. cssAnimationCfg = val;
  88. }
  89. }
  90. });
  91. if (!cssAnimationCfg) {
  92. return;
  93. }
  94. attrs.d = false;
  95. var animationName = addAnimation(composedAnim, scope);
  96. return cssAnimationCfg.replace(cssAnimationName, animationName);
  97. }
  98. function getEasingFunc(easing) {
  99. return isString(easing)
  100. ? EASING_MAP[easing]
  101. ? "cubic-bezier(" + EASING_MAP[easing] + ")"
  102. : createCubicEasingFunc(easing) ? easing : ''
  103. : '';
  104. }
  105. export function createCSSAnimation(el, attrs, scope, onlyShape) {
  106. var animators = el.animators;
  107. var len = animators.length;
  108. var cssAnimations = [];
  109. if (el instanceof CompoundPath) {
  110. var animationCfg = createCompoundPathCSSAnimation(el, attrs, scope);
  111. if (animationCfg) {
  112. cssAnimations.push(animationCfg);
  113. }
  114. else if (!len) {
  115. return;
  116. }
  117. }
  118. else if (!len) {
  119. return;
  120. }
  121. var groupAnimators = {};
  122. for (var i = 0; i < len; i++) {
  123. var animator = animators[i];
  124. var cfgArr = [animator.getMaxTime() / 1000 + 's'];
  125. var easing = getEasingFunc(animator.getClip().easing);
  126. var delay = animator.getDelay();
  127. if (easing) {
  128. cfgArr.push(easing);
  129. }
  130. else {
  131. cfgArr.push('linear');
  132. }
  133. if (delay) {
  134. cfgArr.push(delay / 1000 + 's');
  135. }
  136. if (animator.getLoop()) {
  137. cfgArr.push('infinite');
  138. }
  139. var cfg = cfgArr.join(' ');
  140. groupAnimators[cfg] = groupAnimators[cfg] || [cfg, []];
  141. groupAnimators[cfg][1].push(animator);
  142. }
  143. function createSingleCSSAnimation(groupAnimator) {
  144. var animators = groupAnimator[1];
  145. var len = animators.length;
  146. var transformKfs = {};
  147. var shapeKfs = {};
  148. var finalKfs = {};
  149. var animationTimingFunctionAttrName = 'animation-timing-function';
  150. function saveAnimatorTrackToCssKfs(animator, cssKfs, toCssAttrName) {
  151. var tracks = animator.getTracks();
  152. var maxTime = animator.getMaxTime();
  153. for (var k = 0; k < tracks.length; k++) {
  154. var track = tracks[k];
  155. if (track.needsAnimate()) {
  156. var kfs = track.keyframes;
  157. var attrName = track.propName;
  158. toCssAttrName && (attrName = toCssAttrName(attrName));
  159. if (attrName) {
  160. for (var i = 0; i < kfs.length; i++) {
  161. var kf = kfs[i];
  162. var percent = Math.round(kf.time / maxTime * 100) + '%';
  163. var kfEasing = getEasingFunc(kf.easing);
  164. var rawValue = kf.rawValue;
  165. if (isString(rawValue) || isNumber(rawValue)) {
  166. cssKfs[percent] = cssKfs[percent] || {};
  167. cssKfs[percent][attrName] = kf.rawValue;
  168. if (kfEasing) {
  169. cssKfs[percent][animationTimingFunctionAttrName] = kfEasing;
  170. }
  171. }
  172. }
  173. }
  174. }
  175. }
  176. }
  177. for (var i = 0; i < len; i++) {
  178. var animator = animators[i];
  179. var targetProp = animator.targetName;
  180. if (!targetProp) {
  181. !onlyShape && saveAnimatorTrackToCssKfs(animator, transformKfs);
  182. }
  183. else if (targetProp === 'shape') {
  184. saveAnimatorTrackToCssKfs(animator, shapeKfs);
  185. }
  186. }
  187. for (var percent in transformKfs) {
  188. var transform = {};
  189. copyTransform(transform, el);
  190. extend(transform, transformKfs[percent]);
  191. var str = getSRTTransformString(transform);
  192. var timingFunction = transformKfs[percent][animationTimingFunctionAttrName];
  193. finalKfs[percent] = str ? {
  194. transform: str
  195. } : {};
  196. setTransformOrigin(finalKfs[percent], transform);
  197. if (timingFunction) {
  198. finalKfs[percent][animationTimingFunctionAttrName] = timingFunction;
  199. }
  200. }
  201. ;
  202. var path;
  203. var canAnimateShape = true;
  204. for (var percent in shapeKfs) {
  205. finalKfs[percent] = finalKfs[percent] || {};
  206. var isFirst = !path;
  207. var timingFunction = shapeKfs[percent][animationTimingFunctionAttrName];
  208. if (isFirst) {
  209. path = new PathProxy();
  210. }
  211. var len_1 = path.len();
  212. path.reset();
  213. finalKfs[percent].d = buildPathString(el, shapeKfs[percent], path);
  214. var newLen = path.len();
  215. if (!isFirst && len_1 !== newLen) {
  216. canAnimateShape = false;
  217. break;
  218. }
  219. if (timingFunction) {
  220. finalKfs[percent][animationTimingFunctionAttrName] = timingFunction;
  221. }
  222. }
  223. ;
  224. if (!canAnimateShape) {
  225. for (var percent in finalKfs) {
  226. delete finalKfs[percent].d;
  227. }
  228. }
  229. if (!onlyShape) {
  230. for (var i = 0; i < len; i++) {
  231. var animator = animators[i];
  232. var targetProp = animator.targetName;
  233. if (targetProp === 'style') {
  234. saveAnimatorTrackToCssKfs(animator, finalKfs, function (propName) { return ANIMATE_STYLE_MAP[propName]; });
  235. }
  236. }
  237. }
  238. var percents = keys(finalKfs);
  239. var allTransformOriginSame = true;
  240. var transformOrigin;
  241. for (var i = 1; i < percents.length; i++) {
  242. var p0 = percents[i - 1];
  243. var p1 = percents[i];
  244. if (finalKfs[p0][transformOriginKey] !== finalKfs[p1][transformOriginKey]) {
  245. allTransformOriginSame = false;
  246. break;
  247. }
  248. transformOrigin = finalKfs[p0][transformOriginKey];
  249. }
  250. if (allTransformOriginSame && transformOrigin) {
  251. for (var percent in finalKfs) {
  252. if (finalKfs[percent][transformOriginKey]) {
  253. delete finalKfs[percent][transformOriginKey];
  254. }
  255. }
  256. attrs[transformOriginKey] = transformOrigin;
  257. }
  258. if (filter(percents, function (percent) { return keys(finalKfs[percent]).length > 0; }).length) {
  259. var animationName = addAnimation(finalKfs, scope);
  260. return animationName + " " + groupAnimator[0] + " both";
  261. }
  262. }
  263. for (var key in groupAnimators) {
  264. var animationCfg = createSingleCSSAnimation(groupAnimators[key]);
  265. if (animationCfg) {
  266. cssAnimations.push(animationCfg);
  267. }
  268. }
  269. if (cssAnimations.length) {
  270. var className = scope.zrId + '-cls-' + getClassId();
  271. scope.cssNodes['.' + className] = {
  272. animation: cssAnimations.join(',')
  273. };
  274. attrs["class"] = className;
  275. }
  276. }