useSvgAnimation.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { ref } from 'vue';
  2. import _ from 'lodash';
  3. /**
  4. * svg二维模型动画使用的钩子,需要配合指定的组件使用,即svg模型组件(README里有更详细的说明)
  5. *
  6. * 备注:一个元素的动画仅有两种状态,正常播放、倒放;例如:`triggerAnimation(id1, false)`代表触发id1对应的动画,false代表触发正常播放的动画
  7. */
  8. export function useSvgAnimation(elementInfo: Map<string, { key: string; transforms: string[] }>) {
  9. /** 所有动画元素 */
  10. const animationElements = new Map<string, HTMLElement>();
  11. /** 管理节点是否处于初始状态 */
  12. const animationManager = ref<{ [id: string]: boolean }>({});
  13. /**
  14. * 触发动画函数,该函数用来根据id查找SVG图片中的对应group,然后触发绑定在此group上的动画
  15. *
  16. * 动画有且仅有两个状态,一种是初始状态,一种是结束状态,当动画触发后,会根据reverse传参自动切换状态
  17. *
  18. * @param id 标识符号(可以在页面中使用元素选择器选择具体元素后查询其id),可以传数组
  19. * @param reverse 是否需要反向执行动画,如果id传了数组该参数可以传数组以一一匹配,默认为false
  20. * @param duration 动画持续时长,越长动画执行的越慢
  21. * @param progress 指定动画执行的进度,默认为1,即动画执行到100%,该数字范围为0-1
  22. */
  23. function triggerAnimation(id: string | string[], reverse: boolean | boolean[] = false, duration = 3000, progress = 1) {
  24. const idArray = typeof id === 'string' ? [id] : id;
  25. const reverseArray = typeof reverse === 'boolean' ? idArray.map(() => reverse) : reverse;
  26. idArray.forEach((id, index) => {
  27. if (animationManager.value[id] === undefined) {
  28. animationManager.value[id] = true;
  29. }
  30. const unchanged = animationManager.value[id];
  31. const reverse = reverseArray[index] || false;
  32. // 不指定反向播放且group处于初始状态时播放正常动画
  33. if (!reverse && unchanged) {
  34. animationManager.value[id] = false;
  35. animateByKey(id, true, duration, progress);
  36. return;
  37. }
  38. if (reverse && !unchanged) {
  39. animationManager.value[id] = true;
  40. animateByKey(id, false, duration, progress);
  41. return;
  42. }
  43. });
  44. }
  45. // 直接控制动画的方法
  46. const animateElement = (elementId: string, toEnd: boolean, duration = 3000, progress = 1) => {
  47. const el = animationElements.get(elementId);
  48. const info = elementInfo.get(elementId);
  49. if (el && info && info.transforms.length > 1) {
  50. const endTransform = _.nth(info.transforms, Math.floor(info.transforms.length * progress));
  51. const startTransform = _.nth(info.transforms, -Math.ceil(info.transforms.length * progress));
  52. el.style.transition = `transform ${duration}ms`;
  53. el.setAttribute('transform', toEnd ? endTransform : startTransform);
  54. }
  55. };
  56. // 批量控制同一key的所有元素
  57. const animateByKey = (key: string, toEnd: boolean, duration = 3000, progress = 1) => {
  58. animationElements.forEach((__, elementId) => {
  59. const info = elementInfo.get(elementId);
  60. if (info && info.key === key) {
  61. animateElement(elementId, toEnd, duration, progress);
  62. }
  63. });
  64. };
  65. // watch(
  66. // () => animationManager,
  67. // () => {
  68. // Object.keys(animationManager).forEach((key) => {
  69. // const unchanged = animationManager[key];
  70. // // 找到所有属于这个key的元素
  71. // animateByKey(key, !unchanged);
  72. // });
  73. // },
  74. // { deep: true }
  75. // );
  76. return {
  77. animationElements,
  78. triggerAnimation,
  79. animateElement,
  80. animateByKey,
  81. };
  82. }