【发布时间】:2020-12-27 21:00:54
【问题描述】:
我有一个 3D 轮子,我正在使用 javascript requestAnimationFrame() 函数制作动画。
轮子的样子:
有 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/
( 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 );
$segment.attr( 'data-end-angle', endAngle );
$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 = Math.abs( angle ) % 360;
angle = 360 - angle;
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 >= startAngle && angle < endAngle;
} );
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>
【问题讨论】:
标签: javascript jquery css geometry css-transforms