cssAnimation.js 10 KB

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