为了账号安全,请及时绑定邮箱和手机立即绑定

使用 javascript 根据旋转角度在 3D 圆柱体/轮子上查找段

使用 javascript 根据旋转角度在 3D 圆柱体/轮子上查找段

波斯汪 2023-02-24 15:18:43
我有一个 3D 轮子,我正在使用 javascriptrequestAnimationFrame()函数制作动画。轮子看起来像:有 4 个主要变量需要考虑:items轮子上的段数。spinSpeed旋转速度修改器。将每帧的角度增加/减少乘以该值。spinDuration减速停止前全速旋转动画的长度。spinDirection轮子应该旋转的方向。接受up或down。现在我想使用轮子停止的角度从 DOM 中获取线段(红线相交的地方)。轮段的弧起点和终点角度存储在数据属性中。例如:<div class="wheel__inner">    <div class="wheel_segment" ... data-start-angle="0" data-end-angle="12.85">Item 1</div>    <div class="wheel_segment" ... data-start-angle="12.85" data-end-angle="25.71">Item 2</div>    <div class="wheel_segment" ... data-start-angle="25.71" data-end-angle="38.58">Item 3</div>    ...</div>我通过在每个刻度上存储修改后的角度来跟踪当前的车轮旋转。例如:let wheelAngle = 0;window.requestAnimationFrame( function tick() {    if ( spinDirection === 'up' ) {        wheelAngle += speedModifier;    } else {        wheelAngle -= speedModifier;    }   window.requestAnimationFrame( tick );} );当动画停止时,我尝试通过使用开始和结束角度对旋转和过滤片段进行归一化来获取片段。我将旋转归一化,因为它可以在上方360°和下方移动0°,我使用以下函数执行此操作:function normaliseAngle( angle ) {    angle = Math.abs( angle ) % 360;    angle = 360 - angle; // Invert    return angle;}并像这样使用 jQuery 过滤元素:const $found = $wheel.find( '.wheel__segment' ).filter( function() {    const startAngle = parseFloat( $( this ).data( 'start-angle' ) );    const endAngle = parseFloat( $( this ).data( 'end-angle' ) );    return angle >= startAngle && angle < endAngle;} );然而,尽管我尽了最大的努力,我还是无法让它发挥作用。请在此处查看我的 JSFiddle:https ://jsfiddle.net/thelevicole/ps04fnxm/2/
查看完整描述

2 回答

?
开心每一天1111

TA贡献1836条经验 获得超13个赞

有两个问题。

下图显示了状态wheelAngle = 0

//img1.sycdn.imooc.com//63f8651100011f4804660308.jpg

在您的代码中,项目 0 具有startAngle = 0和endAngle = some positive value。这与您所看到的不符。实际上,项目 0 应该以 0 为中心。所以你需要将你的区域偏移项目角度宽度的一半:


var rotateAngle = angle * i;        

var transform = `rotateX(${ rotateAngle }deg) translateZ(${ radius }px)`;

var startAngle = rotateAngle - angle / 2

var endAngle = rotateAngle + angle / 2;

第二个问题是您的规范化功能。您采用绝对值,因此会丢失任何方向信息。这是该功能的更好版本:


function normaliseAngle( angle ) {

    angle = -angle;

    return angle - 360 * Math.floor(angle / 360);

}


查看完整回答
反对 回复 2023-02-24
?
函数式编程

TA贡献1807条经验 获得超9个赞

主要问题是开始/结束角度。我更新了如下逻辑:


$segment.attr('data-start-angle', -startAngle + angle / 2);

$segment.attr('data-end-angle', -endAngle + angle / 2);

并且


function normaliseAngle(angle) {

    angle = angle % 360;

    if (angle > 0)

      angle = angle - 360;

    return angle;

  }

负旋转将向您显示从第一个元素开始的元素(而不是正旋转)。您还需要考虑偏移量,angle / 2因为 startAngle 会将您置于元素的中间。然后你应该在逻辑上将你的角度归一化为负值。


完整代码


(function($) {


  // Settings

  const items = 28; // Segments on wheel

  const spinSpeed = randNumber(1, 10); // Spin speed multiplier

  const spinDuration = randNumber(2, 5); // In seconds

  const spinDirection = randNumber(0, 1) ? 'up' : 'down'; // Animate up  or down


  // Vars

  const $wheel = $('.wheel .wheel__inner');

  const diameter = $wheel.height();

  const radius = diameter / 2;

  const angle = 360 / items;

  const circumference = Math.PI * diameter;

  const height = circumference / items;


  // Trackers

  let wheelAngle = 0;

  const wheelStarted = new Date();


  // Add segments to the wheel

  for (let i = 0; i < items; i++) {

    var startAngle = angle * i;

    var endAngle = angle * (i + 1);

    var transform = `rotateX(${ startAngle }deg) translateZ(${ radius }px)`;


    var $segment = $('<div>', {

      class: 'wheel__segment',

      html: `<span>Item ${ i }</span>`

    }).css({

      'transform': transform,

      'height': height,

    });


    // Add start and end angles for this segment

    $segment.attr('data-start-angle', -startAngle + angle / 2);

    $segment.attr('data-end-angle', -endAngle + angle / 2);


    $segment.appendTo($wheel);

  }



  /**

   * Print debug info to DOM

   *

   * @param {object}

   */

  function logInfo(data) {

    const $log = $('textarea#log');

    let logString = '';


    logString += '-----' + "\n";

    for (var key in data) {

      logString += `${ key }: ${ data[ key ] }` + "\n";

    }

    logString += "\n";


    // Prepend log to last value

    logString += $log.val();


    // Update field value

    $log.val(logString);

  }


  /**

   * Get random number between min & max (inclusive)

   *

   * @param {number} min

   * @param {number} max

   * @returns {number}

   */

  function randNumber(min, max) {

    min = Math.ceil(min);

    max = Math.floor(max);

    return Math.floor(Math.random() * (max - min + 1)) + min;

  }


  /**

   * Limit angles to 0 - 360

   *

   * @param {number}

   * @returns {number}

   */

  function normaliseAngle(angle) {

    angle = angle % 360;

    if (angle > 0)

      angle = angle - 360;

    return angle;

  }


  /**

   * Get the wheel segment at a specific angle

   *

   * @param {number} angle

   * @returns {jQuery}

   */

  function segmentAtAngle(angle) {


    angle = normaliseAngle(angle);


    const $found = $wheel.find('.wheel__segment').filter(function() {

      const startAngle = parseFloat($(this).data('start-angle'));

      const endAngle = parseFloat($(this).data('end-angle'));

      return angle >= endAngle && angle < startAngle;

    });


    return $found;

  }


  /**

   * @var {integer} Unique ID of requestAnimationFrame callback

   */

  var animationId = window.requestAnimationFrame(function tick() {


    // Time passed since wheel started spinning (in seconds)

    const timePassed = (new Date() - wheelStarted) / 1000;


    // Speed modifier value (can't be zero)

    let speedModifier = parseInt(spinSpeed) || 1;


    // Decelerate animation if we're over the animation duration

    if (timePassed > spinDuration) {


      const decelTicks = (spinDuration - 1) * 60;

      const deceleration = Math.exp(Math.log(0.0001 / speedModifier) / decelTicks);

      const decelRate = (1 - ((timePassed - spinDuration) / 10)) * deceleration;


      speedModifier = speedModifier * decelRate;


      // Stop animation from going in reverse

      if (speedModifier < 0) {

        speedModifier = 0;

      }

    }


    // Print debug info

    logInfo({

      timePassed: timePassed,

      speedModifier: speedModifier,

      wheelAngle: wheelAngle,

      normalisedAngle: normaliseAngle(wheelAngle)

    });


    // Wheel not moving, animation must have finished

    if (speedModifier <= 0) {

      window.cancelAnimationFrame(animationId);


      const $stopped = segmentAtAngle(wheelAngle);

      alert($stopped.text());


      return;

    }


    // Increase wheel angle for animating upwards

    if (spinDirection === 'up') {

      wheelAngle += speedModifier;

    }


    // Decrease wheel angle for animating downwards

    else {

      wheelAngle -= speedModifier;

    }


    // CSS transform value

    const transform = `rotateX(${wheelAngle}deg) scale3d(0.875, 0.875, 0.875)`;


    $wheel.css({

      '-webkit-transform': transform,

      '-moz-transform': transform,

      '-ms-transform': transform,

      '-o-transform': transform,

      'transform': transform,

      'transform-origin': `50% calc(50% + ${height/2}px)`,

      'margin-top': `-${height}px`

    });


    // New tick

    animationId = window.requestAnimationFrame(tick);

  });


})(jQuery);

*,

*:before,

*:after {

  box-sizing: border-box;

}


.app {

  display: flex;

  flex-direction: row;

  padding: 15px;

}


textarea#log {

  width: 300px;

}


.wheel {

  perspective: 1000px;

  border: 1px solid #333;

  margin: 0 25px;

  flex-grow: 1;

}


.wheel:after {

  content: '';

  display: block;

  position: absolute;

  top: 50%;

  left: 0;

  right: 0;

  height: 2px;

  background-color: red;

  transform: translateY(-50%);

}


.wheel .wheel__inner {

  position: relative;

  width: 200px;

  height: 350px;

  margin: 0 auto;

  transform-style: preserve-3d;

}


.wheel .wheel__inner .wheel__segment {

  display: flex;

  justify-content: center;

  align-items: center;

  width: 100%;

  height: 40px;

  position: absolute;

  top: 50%;

  background-color: #ccc;

}


.wheel .wheel__inner .wheel__segment:nth-child(even) {

  background-color: #ddd;

}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div class="app">

  <textarea id="log"></textarea>

  <div class="wheel">

    <div class="wheel__inner">

    </div>

  </div>

</div>


查看完整回答
反对 回复 2023-02-24
  • 2 回答
  • 0 关注
  • 69 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信