动态旋转与平滑恢复

动态旋转与平滑恢复

需求:使用CSS+JavaScript实现以下Web效果:

  1. 三个DOM元素:一张图片,两个按钮
  2. 点击开始按钮,使图片开始不停旋转(rotate0 -> rotate360)
  3. 点击停止按钮,图片停止旋转,并平滑恢复到初始状态

小结:

  1. 抽象了函数 getMatrixFromDegree() 和 getDegreeFromMatrix() 用于矩阵和角度间的转换
  2. 需要加深理解 transform 和 matrix 的关系原理

实现:

<html>
<body>
  <div id="target">G</div>
  <button id="play">PLAY</button>
  <button id="pause">PAUSE</button>
  <style>
    body {
      background: #333;
    }
    button {
      width: 200px;
      height: 50px;
      font-size: 30px;
    }
    #target {
      width: 200px;
      height: 200px;
      font-size: 170px;
      line-height: 170px;
      text-align: center;
      border-radius: 50%;
      color: #fff;
      background: lightgreen;
    }
    #target.play {
      animation-name: rotateTarget;
      animation-iteration-count: infinite;
      animation-timing-function: linear;
      animation-duration: 5s;
    }
    @keyframes rotateTarget {
      0%   { transform: rotate(0deg); }
      100% { transform: rotate(360deg); }
    }
    /* 
    由于 transform matrix 控制的角度从 180 迈向 180+ 会发生一个未知的回转现象
    故不采用 matrix 进行角度旋转的控制
    0%    { transform: matrix( 1, 0, 0, 1, 0, 0); }
    25%   { transform: matrix( 0, 1,-1, 0, 0, 0); }
    50%   { transform: matrix(-1, 0, 0,-1, 0, 0); }
    75%   { transform: matrix( 0,-1, 1, 0, 0, 0); }
    100%  { transform: matrix( 1, 0, 0, 1, 0, 0); }
      */
  </style>
  <script>
    const getMatrixFromDegree = (degreeValue) => {
      // transform中Matrix的学习可参考:
      // https://www.zhangxinxu.com/wordpress/2012/06/css3-transform-matrix-矩阵/
      const cosVal = Math.cos( this.value * Math.PI / 180 ).toFixed(6);
      const sinVal = Math.sin( this.value * Math.PI / 180 ).toFixed(6);
      const transform = `matrix(${cosVal},${sinVal},${-1 * sinVal},${cosVal},0,0)`;
      return transform;
    }
    
    const getDegreeFromMatrix = (matrixString) => {
      // degree的精确度参考:https://developer.mozilla.org/en-US/docs/Web/CSS/angle
      const reg     = matrixString.match(/matrix\((.*?), (.*?),/);
      const scaleX  = +reg[1];
      const skewY   = +reg[2];
      const degree  = +(Math.acos(scaleX) * 180 / Math.PI).toFixed(2);
      if (skewY > 0) {
        return degree;
      } else {
        return 360 - degree;
      }
    }

    const target = document.getElementById('target');
    const play = document.getElementById('play');
    const pause = document.getElementById('pause');

    play.onclick = () => {
      target.className = 'play';
    }
    pause.onclick = () => {
      const smoothTime = 0.5; // 平滑恢复一周所用时间
      const currentTransform = window.getComputedStyle(target).getPropertyValue('transform');

      const currentDegree = getDegreeFromMatrix(currentTransform);
      const percent = +(currentDegree / 360).toFixed(2);
      const runTime = smoothTime * (1 - percent);
      const getHalfKeyFrame = () => {
        if (currentDegree < 180) {
          const halfPercent = (((0.5 - percent) / (1 - percent)) * 100).toFixed(2);
          return `${halfPercent}% { transform: rotate(180deg); }`;
        } else {
          return '';
        }
      };

      const runkeyframes = `
        #target.pause {
          animation-name: resetTargetSmooth;
          animation-timing-function: linear;
          animation-duration: ${runTime}s;
          will-change: auto;
        }
        @keyframes resetTargetSmooth {
          0%          { transform: rotate(${currentDegree}deg); }
          ${getHalfKeyFrame()}
          100%        { transform: rotate(360deg); }
        }`;

      const style = document.createElement('style');
      style.type = 'text/css';
      style.innerHTML = runkeyframes;
      document.body.appendChild(style);

      target.onanimationend = (e) => { // 动画结束时刻清理
        if (e.animationName === 'resetTargetSmooth') {
          document.body.removeChild(style)
        }
      };

      const animationFunc = function () {
        target.className = 'pause';
      }

      requestAnimationFrame(animationFunc);
    }
  </script>
</body>
</html>
posted @ 2021-02-01 13:46  汪淼焱  阅读(119)  评论(0编辑  收藏  举报