如何添加惯性跟踪鼠标移动的动画? [英] How to add inertia to animations tracking mouse move?

查看:276
本文介绍了如何添加惯性跟踪鼠标移动的动画?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试使用mouseMove事件绕原点旋转三角形。

我得到触摸起点当前触点使用 touchstart touchmove 事件,那么我很容易使用方向找到方向角:

  alpha = y2  -  y1 / x2  -  x1; // alpha是角度的正切
beta = atan(alpha); // beta是以弧度表示的角度

然后我旋转 PIXI

  function animTriangle(beta){

最初清除先前的形状
myTriangle.clear();


//在新位置绘制一个新形状
myTriangle.beginFill(0x000000,0.1);
myTriangle.moveTo(origin.x,origin.y);
myTriangle.lineTo(origin.x - 100,0);
myTriangle.lineTo(origin.x + 100,0);
myTriangle.lineTo(origin.x,origin.y);
myTriangle.endFill();
myTriangle.rotation = beta;

}

我使用 RequestAnimationFrame 循环。



问题是动画是不停的,我需要惯性旋转。如何修复此函数?

解决方案

惯性,加速度和拖动
$ b

我使用的方法是创建一个追逐值,使用模拟加速度和拖曳(电阻)值的deltaV来追踪所需的值。注意这是一个简单的模拟。



一步一步



  var rotate =? //用户输入的值
var rotateChase; //这是代表显示控件的追逐值
var rotateDelta; //这是每帧chase的变化
const rotateDrag = 0.4; //这是摩擦或拖动
const rotateAcceleration = 0.9; //这是显示响应的速度

拖动是大于0的值, 1,更像一个阻尼弹簧的功能。小于0.5的值为计数值提供平滑停止。值> = 0.5导致追逐值围绕所需值反弹,随着拖动值向1移动,反弹变得越来越明显。



加速度值为范围大于0和< = 1。这是响应改变追逐值的方式。低值会使控件看起来缓慢或重。较高的值使得追逐快速响应,看起来很轻。



拖动和加速都会互相影响。
For




  • 快速反应 accel = 0.9 drag = 0.49

  • 光慢响应 accel = 0.1 drag = 0.49

  • 重缓慢响应 accel = 0.02 drag = 0.49

  • 硬弹性回应 accel = 0.7 drag = 0.7

  • 慢速弹性回应 accel = 0.1 drag = 0.7




更新



  rotateDelta + =(rotate  -  rotateChase)* rotateAcceleration; 

通过减少其偏差将拖动添加到增量。

  rotateDelta * = rotateDrag; 

然后将deltaV添加到追逐值中,

  rotateChase + = rotateDelta; 

现在可以显示追逐值

  myTriangle.rotation = rotateChase; 

添加边界



这是一个未绑定的追逐者。要设置边界的值需要一些额外的代码。首先通过设置最小和最大值来描述边界。

  var rotateMin = 0; 
var rotateMax = Math.PI * 2;

然后描述如果值通过定义反射或反弹超过边界的行为。 0使其在边界处停止, 0和> = -1将给出从末端的小反弹。其他值创建有趣的FX

  var rotateReflect = -0.5; 

然后是控制该行为的代码

  if(rotateChase< rotateMin){
rotateChase = rotateMin; //设置为最小val
if(rotateDelta< 0){//只有delta为负
rotateDelta * = rotateReflect;
}
} else
if(rotateChase> rotateMax){
rotateChase = rotateMax; //设置为max
if(rotateDelta> 0){//只有delta是正的
rotateDelta * = rotateReflect;
}
}

显示和使用 / p>

现在您只需要选择要用作控件输入的值。 rotateChase rotate 都可以使用,但 rotateChase 有点时间安定下来。我所做的是从 rotateChase 值中得到一个四舍五入值,它删除了追逐的细节。



示例如果控件是用于卷

  device.volume = Number(rotateChase.toFixed(3)); 

方便



所有接缝像一个值的很多工作。但是我们是程序员和固有的惰性,所以让它分成一个简单的惯性类

  //定义惯性对象。设置答案详细信息。 
//有方法
//更新(输入);一旦动画帧被调用,输入值为chase
// setValue(input);硬设置追逐值。不拖动或惯性
//有属性
//值;追求值bounds checked
函数Inertia(min,max,acceleration,drag,reflect){
//一些可能会问为什么常量,为什么不使用闭包对参数
//原因:如果输入
//参数更改,某些JS环境将无法优化代码。可能很容易将此对象扩展为
//更改min,max或其他。我把这里强调
//事实,参数闭包变量不应该修改
//如果性能很重要。
const ac = acceleration; // set constants
const dr = drag;
const minV = min;
const maxV = max;
const ref = -Math.abs(reflect); //确保负数。为什么?因为我总是忘记这是一个消极的。
this.value = min;
var delta = 0;

this.update = function(input){
delta + =(input - this.value)* ac;
delta * = dr;
this.value + = delta;
if(this.value< minV){
this.value = minV;
if(delta< 0){
delta * = ref;
}
} else
if(this.value> maxV){
this.value = maxV;
if(delta> 0){
delta * = ref;
}
}
return this.value;
};
//将值移动到所需的值,没有任何惯性或拖动
//被绑定检查
this.setValue = function(input){
delta = 0;
this.value = Math.min(maxV,Math.min(minV,input));
return this.value;
}
}

使用上述代码

  // in init 
var rotater = new Inertia(0,Math.PI * 2,0.9,0.4,-0.1);

//在动画框架中
myTriange = rotater.update(beta);

UPDATE



我添加了一些代码来显示各种设置以及它们如何影响惯性。该代码不打算作为代码样式或DOM接口最佳实践的例子,因为它在这两个计数上非常短。它使用上面提出的惯性对象。



全萤幕



  // ------------------------------ ------------------------ //来自答案的函数惯性//定义一个惯量对象。设置答案细节//有方法//更新(输入);一旦动画帧被调用,输入值为chase // set(input);硬设置追逐值。不拖动或惯性//有属性//值;追逐值界限检查函数惯量(最小,最大,加速,拖动,反射){//有些人可能会问为什么常量,为什么不使用闭包的参数//原因:一些JS环境将无法优化代码,如果输入/ /参数更改。可能很容易将此对象扩展为//更改min,max或其他。我把这里强调的事实,参数闭包变量不应该修改//如果性能很重要。 const ac = acceleration; // set constants const dr = drag; const minV = min; const maxV = max; const ref = -Math.abs(reflect); //确保负数。为什么?因为我总是忘记这是一个消极的。 this.value = min; this.quiet = true; var delta = 0; this.update = function(input){delta + =(input  -  this.value)* ac; delta * = dr; this.value + = delta; if(this.value< minV){this.value = minV; if(delta< 0){delta * = ref; }} else if(this.value> maxV){this.value = maxV; if(delta> 0){delta * = ref; }} if(Math.abs(delta)<(maxV-minV)* 0.001&&&& Math.abs(this.value-input)<0.1){this.quiet = true; } else {this.quiet = false; } return this.value; }; //将值移动到所需的值,没有任何惯性或拖动//被绑定检查this.setValue = function(input){delta = 0; this.quiet = true; this.value = Math.min(maxV,Math.max(minV,input)); return this.value; }} //答案结束// ----------------------------------------- --------------- //所有以下代码不是答案的一部分//我没有格式化,评论和彻底测试它/ ** MouseFullDemo.js开始** / var canvasMouseCallBack = undefined; // if needed function createMouse(element){var demoMouse =(function(){var mouse = {x:0,y:0,w:0,alt:false,shift:false,ctrl:false,lx:0,ly :0,interfaceId:0,buttonLastRaw:0,buttonRaw:0,over:false,//鼠标在元素上方bm:[1,2,4,6,5,3],//用于设置和清除按钮原始位; getInterfaceId:function(){return this.interfaceId ++;},//对于UI函数startMouse:undefined,}; function mouseMove(e){//console.log(e)var t = e.type,m =小鼠; m.lx = e.offsetX; m.ly = e.offsetY; mx = e.clientX; my = e.clientY; m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey; if(t ===mousedown){m.buttonRaw | = m.bm [e.which-1];} else if(t ===mouseup){m.buttonRaw& m.bm [e.which + 2];} else if(t ===mouseout){m.over = false;} else if(t ===mouseover){m.over = true; } else if(t ===mousewheel){m.w = e.wheelDelta; } else if(t ===DOMMouseScroll){m.w = -e.detail;} if(canvasMouseCallBack){canvasMouseCallBack(m.x,m.y); } e.preventDefault(); } function startMouse(element){if(element === undefined){element = document; }mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll.split(,)forEach(function(n){element.addEventListener(n,mouseMove);}); element.addEventListener(contextmenu,function(e){e.preventDefault();},false); } mouse.mouseStart = startMouse;返回鼠标})(); demoMouse.mouseStart(element); return demoMouse;} / ** MouseFullDemo.js end ** / var cellSize = 70; var createImage = function(w,h){var i = document.createElement(canvas); i.width = w; i.height = h; i.ctx = i.getContext(2d); return var; return i;} var drawCircle = function(img,x,y,r,col,colB,col1,width){var c = img.ctx; var g; c.lineWidth = width; c.strokeStyle = col1; g = c.createRadialGradient(x,y,1,x,y,r); g.addColorStop(0,col); g.addColorStop(1,colB); c.fillStyle = g c.beginPath(); c.arc(x,y,r-width * 3,0,Math.PI * 2); c.fill(); c.strokeStyle = col1; c.fillStyle = col1; c.fillRect(x,y-width,r,width * 2)c.fillStyle = col; c.fillRect(x + width,y-width / 2,r,width)} var drawCircleO = function(img,x,y,r,col,colB,col1,width){var c = img.ctx; var g = c.createRadialGradient(x + r * 0.21,y + r * 0.21,r * 0.7,x + r * 0.21,y + r * 0.21,r) g.addColorStop(0,black); g.addColorStop(1,rgba(0,0,0,0)); c.fillStyle = g; c.globalAlpha = 0.5; c.beginPath(); c.arc(x + r * 0.21,y + r * 0.21,r,0,Math.PI * 2); c.fill(); c.globalAlpha = 1; var g = c.createRadialGradient(x * 0.3,y * 0.3,r * 0.5,x * 0.3,y * 0.3,r) g.addColorStop(0,col); g.addColorStop(1,colB); c.lineWidth = width; c.strokeStyle = col1; c.fillStyle = g; c.beginPath(); c.arc(x,y,r-width,0,Math.PI * 2); stroke(); c.fill();} //用矿工标记绘制径向标记// len,col和width是arraysvar drawCircleMarks = function(img,x,y,r,start,end,col,width,length,number,miner) {var i,vx,vy,count,style,len; var c = img.ctx; var step =(end-start)/ number; count = 0; end + = step / 2; (i = start; i <= end; i + = step){vx = Math.cos(i); //添加到结束以考虑浮点舍入误差vy = Math.sin(i); if(count%miner === 0){style = 0; } else {style = 1; } c.strokeStyle = col [style]; c.lineWidth = width [style]; len = length [style]; c.beginPath(); c.moveTo(vx * r + x,vy * r + y); c.lineTo(vx *(r + len)+ x,vy *(r + len)+ y); stroke(); count + = 1; }} var defaultMap = {number:function(num,def){if(isNaN(num)){return def} return Number(num) },color:function(col,def){//没有多少代码演示,所以删除if(col === undefined || typeof col!==string){return def; } return col; },ticks:{validate:function(val){return val === undefined?true:val?true:false; };},type:{validate:function(val){switch(val){casedial:casehorizo​​ntal-slider:return val; } return undefined}},min:{validate:function(val){return defaultMap.number(val,0); }},max:{validate:function(val){return defaultMap.number(val,100); }},drag:{validate:function(val){return defaultMap.number(val,0.4); }},reflect:{validate:function(val){return defaultMap.number(val,0.2); }},accel:{validate:function(val){return defaultMap.number(val,0.4); }},value:{validate:function(val){return defaultMap.number(val,0); }},tick-color:{validate:function(val){}},decimals:{validate:function(val){return defaultMap.number(val,0); }},display:{validate:function(val){if(val === null || val === undefined || typeof val!==string){return undefined; } return document.querySelector(val); }}} //验证用户定义的DOM属性函数getSafeAttribute(element,name){var val,def; if(name === undefined){return undefined; } def = defaultMap [name]; if(def === undefined){//未知属性if(element.attributes [data  - + name]){return element.attributes [data  - + name] .value; } return undefined} if(element.attributes [data  - + name]){val = element.attributes [data  - + name] .value; } return def.validate(val);} //自定义用户控件//警告这可以返回一个未定义的控制函数Control(element,owner){var dialUpdate,drawFunc,w,h,nob, chaser,dragging,dragX,dragY,dragV,realValue,startP,endP,lastVal,reflect,drag,accel; var dialUpdate = function(){var unitPos =(this.value-this.min)/(this.max-this.min); canvas.ctx.setTransform(1,0,0,1,0,0); canvas.ctx.clearRect(0,0,w,h); canvas.ctx.drawImage(back,0,0); canvas.ctx.setTransform(1,0,0,1,back.width / 2,back.height / 2); canvas.ctx.rotate(unitPos *(endP-startP)+ startP); canvas.ctx.drawImage(nob,-nob.width / 2,-nob.height / 2); } if(element === undefined){//给我的UNI导师与爱.. LOL返回未定义; } this.type = getSafeAttribute(element,type); if(this.type === undefined){return undefined; //这是一个非标准的返回} this.owner = owner; //暴露所有者//暴露的属性this.min = getSafeAttribute(element,min); this.max = getSafeAttribute(element,max); this.ticks = getSafeAttribute(element,ticks); this.tickColor = getSafeAttribute(element,tick-color); this.value = realValue = getSafeAttribute(element,value); this.display = getSafeAttribute(element,display); if(this.display){var decimals = getSafeAttribute(element,decimals); } drag = getSafeAttribute(element,drag); accel = getSafeAttribute(element,accel); reflect = getSafeAttribute(element,reflect);; chaser = new Inertia(this.min,this.max,accel,drag,reflect); w = element.offsetWidth; h = element.offsetHeight; canvas = createImage(w,h); minSize = Math.min(w,h); mouse = createMouse(element); if(this.type ===dial){nob = createImage(minSize *(3/4),minSize *(3/4)); drawCircle(nob,minSize *(3/4)*(1/2),minSize *(3/4)*(1/2),minSize *(3/4)*(1/2) #CCC,black,3); back = createImage(minSize,minSize); startP = Math.PI *(3/4); endP = Math.PI *(9/4); drawCircleMarks(back,minSize / 2,minSize / 2,minSize / 3,startP,endP,[black,#666],[2,1],[minSize *(1/4),minSize * / 9)],16,4); drawCircleO(back,minSize *(1/2),minSize *(1/2),minSize *(3/4)*(1/2),white,#aaa,black drawFunc = dialUpdate.bind(this); } element.appendChild(canvas); this.active = true; this.resetChaser = function(min,max,accel1,drag1,reflect1){this.min = min === null?this.min:min; this.max = max === null?this.max:max; drag = drag1 === null?drag:drag1; accel = accel1 === null?accel:accel1; reflect = reflect1 === null?reflect:reflect1; chaser = new Inertia(this.min,this.max,accel,drag,reflect); chaser.setValue(this.value); drawFunc(); } this.update = function(){var inVal; if(mouse.over){element.style.cursor =drag_ew; } if((this.owner.mouse.buttonRaw& 1)=== 1&&!dragging&&& mouse.over&&& this.owner.draggingID === -1){dragX = this .owner.mouse.x  - (mouse.lx-w / 2); dragY = this.owner.mouse.y  - (mouse.ly-h / 2); dragging = true; this.owner.draggingID = this.ID; } else if(this.owner.draggingID === this.ID&&(this.owner.mouse.buttonRaw& 1)=== 1 ||(this.owner.mouse.buttonRaw& 1)== = 0)&&&&& drag){inVal =(Math.atan2(this.owner.mouse.y-dragY,this.owner.mouse.x-dragX)+ Math.PI * 2); if(inVal> Math.PI * 0.5 + Math.PI * 2){inVal  -  = Math.PI * 2; } realValue = inVal; realValue =((realValue-startP)/(endP-startP))*(this.max-this.min)+ this.min; if((this.owner.mouse.buttonRaw& 1)=== 0){dragging = false; this.owner.draggingID = -1; }} realValue = Math.min(this.max,Math.max(this.min,realValue)); this.value = chaser.update(realValue); if(!chaser.quiet){drawFunc(); if(this.display){this.display.textContent = realValue.toFixed(decimals); } if(this.onchange!== undefined&& typeof this.onchange ===function){this.onchange({value:realValue,target:element,control:this}); }}} //强制chaser唤醒chaser.setValue(this.value); drawFunc(); element.control = this;} //找到并创建controllersfunction控制器(名称){var controls,elems,i,control,e; var ID = 0; controls = []; elems = document.querySelectorAll(。+ name); for(i = 0; i  

  .testControl {width:110px; height:110px; display:inline-block; text-align:center;}。big {width:200px; height:200px; } .demo {text-align:center;}  

  ; div class =demo>< h3>变量拖动和加速设置示例< / h3>< p>单击控件以更改设置。单击拖动以调整设置。前两行是预设的。 < b> D< / b>和< b> A< / b> < / p>< span class =testControldata-drag =< / p> 0.1data-accel =0.9data-value =0data-type =dial>< / b>:0.1 b& / span>< span class =testControldata-drag =0.2data-accel =0.8data-value =0data-type =dial>< b> D< / b> :0.2< / b>:0.8< / span>< span class =testControldata-drag =0.3data-accel =0.7data-value =0data-type =dial>< b> D< / b>:0.3< / b>:0.7< / span>< span class =testControl accel =0.6data-value =0data-type =dial>< b> D< / b>:0.4< / b>:0.6< / span> span class =testControldata-drag =0.5data-accel =0.5data-value =0data-type =dial>< b> D< / b>:0.5& ; A< / b>:0.5< / span>< span class =testControldata-drag =0.6data-accel =0.4data-value =0data-type =dial> ; b:0.6 b:0.4< / span>< span class =testControldata-drag =0.7data-accel =0.3 data-value =0data-type =dial>< b> D< / b>:0.7< b> A< / b>:0.3< / span> data-drag =0.8data-accel =0.2data-value =0data-type =dial>< b>:0.8< b& ;:0.2< / span>< span class =testControldata-drag =0.9data-accel =0.1data-value =0data-type =dial>< b&数据 - 加速度=0.9数据 - 拖动=0.9数据 - 拖动=0.9数据 - 拖动-value =0data-type =dial>< b> D< / b>:0.9< b> A< / b>:0.9< / span>< span class =testControl data-drag =0.8data-accel =0.8data-value =0data-type =dial>< / b> ;:0.8 b& :0.8< / span>< span class =testControldata-drag =0.7data-accel =0.7data-value =0data-type =dial>< b> ; / b>:0.7 b< / b>:0.7< / span>< span class =testControldata-drag =0.6data-accel =0.6data-value = < / span>< / span>< span class =testControldata-drag =;data-type =dial>< 0.5数据-accel =0.5数据值=0数据类型=dial> D< / b>:0.5 b A: span>< span class =testControldata-drag =0.4data-accel =0.4data-value =0data-type =dial>< b> D< / b> ;: 0.4 b:A< / b>:0.4< / span>< span class =testControldata-drag =0.3data-accel =0.3data-value = dial>< b> D< / b>:0.3< b> A< / b>:0.3< / span>< span class =testControldata-drag =0.2data-accel =0.2data-value =0data-type =dial>< b>:< / b>:0.2< / b>:0.2< / span& class =testControldata-drag =0.1data-accel =0.1data-value =0data-type =dial>< b> D< / b>:0.1& A< / b>:0.1< / span>< br>< h3>以下3个拨盘控制大号表盘的惯性设置< / h3>< span class =testControlid =dragSettingdata -value =0data-display =#display-dragdata-decimals =3data-min =0data-max =1data-type =dial> Drag& < span class =testControlid =accelSettingdata-value =0data-display =#display-accel data-decimals =3data-min =0data-max =1data-type =dial> Accel< span id =display-accel> 0.000< / span> ; / span>< span class =testControlid =reflectSettingdata-value =0data-display =#display-reflectdata-decimals =3data-min = max =1data-type =dial>反映< span id =display-reflect> 0.000< / span>< / span>< / div>< div class = >< div class =testControl bigid =bigDialdata-drag =0.1data-accel =0.1data-value =0data-type =dial> id =bigDialText> Letf点击拖动以更改< / span>< / div>< br>< / div>  

div>



希望这有帮助。


I'm trying to rotate a triangle around an origin point using mouseMove event.
I'm getting the touch start point and the current touch point using touchstart and touchmove events, then I'm finding the angle of direction easily using:

alpha = y2 - y1 / x2 - x1;     // alpha is the tangent of the angle
beta= atan(alpha);      // beta is the angle in radians

Then I'm rotating the element in PIXI:

function animTriangle (beta) {

  // initially clear the previous shape
  myTriangle.clear();


  // draw a new shape in new positions
  myTriangle.beginFill(0x000000, 0.1);
  myTriangle.moveTo(origin.x, origin.y);
  myTriangle.lineTo(origin.x - 100, 0);
  myTriangle.lineTo(origin.x + 100, 0);
  myTriangle.lineTo(origin.x, origin.y);
  myTriangle.endFill();
  myTriangle.rotation = beta;

}

I'm managing my paintings using RequestAnimationFrame loop.

The problem is the animation is choppy and I need inertia in rotation. How could fix this function?

解决方案

Inertia, acceleration, and drag

A method I use is to create a chasing value that chases the desired value using a deltaV that simulates acceleration and drag (resistance) values. Note this is a simple simulation.

Step by step

Defining required values

var rotate = ?; // the value input by the user
var rotateChase; // this is the chasing value representing the displayed control
var rotateDelta; // this is the change in chase per frame
const rotateDrag = 0.4; // this is the friction or drag
const rotateAcceleration = 0.9; // this is how quickly the display responds

The drag is a value greater than 0 and <= 1 and is more like a damping spring in function. Values less than 0.5 provide a smooth stop to the counting value. Values >= 0.5 cause the chase value to bounce around the required value, as the drag value moves towards 1 the bounce become more and more pronounced.

The acceleration value is a range greater than 0 and <= 1. This is how responsive to change the chase value is. Low values will make the control seem sluggish or heavy. Higher values make the chase quick to respond and seem light to the touch.

Drag and Acceleration both effect each other. For

  • quick light response accel = 0.9, drag = 0.49
  • light slow response accel = 0.1, drag = 0.49
  • heavy slow response accel = 0.02, drag = 0.49
  • hard springy response accel = 0.7, drag = 0.7
  • slow springy response accel = 0.1, drag = 0.7

Updating

Then once a frame, accelerate towards the input value by adding to deltaV,

rotateDelta += (rotate - rotateChase) * rotateAcceleration;

Add drag to the delta by reducing its mangitude.

rotateDelta *= rotateDrag;

Then add the deltaV to the chasing value,

rotateChase += rotateDelta;

Now it is ready to display the chasing value

myTriangle.rotation = rotateChase;

Adding boundaries

That is an unbound chaser. To set bounds to the value needs some extra code. First describe the bounds by setting min and max values.

var rotateMin = 0;
var rotateMax = Math.PI*2;

Then describe the behaviour if the value crosses the bounds by defining the reflection or bounce. 0 makes it stop dead at the bounds, < 0 and >= -1 will give a small bounce from the ends. Other values create interesting FX

var rotateReflect = -0.5;  

and then the code to control that behaviour

if (rotateChase < rotateMin) { 
   rotateChase = rotateMin;  // set to the min val
   if(rotateDelta < 0){      // only if delta is negative
       rotateDelta *= rotateReflect;
   }
}else
if (rotateChase > rotateMax) {
   rotateChase = rotateMax;  // set to the max
   if(rotateDelta > 0){      // only if delta is positive
       rotateDelta *= rotateReflect;
   }
}

Display and use

You are now just left with the choice of which value to use as the input of the control. rotateChase and rotate can both be used but rotateChase can take a little time to settle down to. What I do is get a rounded value from the rotateChase value which removes the fine detail of the chase.

For example if the control is for a volume

device.volume = Number(rotateChase.toFixed(3));

Making it easy

That all seams like a lot of work for one value. But we are programmers and inherently lazy, so lets compartmentalise it into a simple inertia class

// Define a Inertia object. Set Answer for details.
// Has methods 
// update(input); Called once pre animation frame with input being the value to chase
// setValue(input); Hard sets the chasing value. Not drag or inertia
// Has properties
// value;  The chasing value bounds checked
function Inertia (min, max, acceleration, drag, reflect) {
    // some may question why the constants, why not use the closure on arguments
    // Reason: Some JS environments will fail to optimise code if the input
    //         arguments change. It may be tempting to extend this Object to 
    //         change the min, max or others. I put this here to highlight the
    //         fact that argument closure variables should not be modified
    //         if performance is important.
    const ac = acceleration;  // set constants
    const dr = drag;
    const minV = min;
    const maxV = max;
    const ref = -Math.abs(reflect); // ensure a negative. Why? because I always forget this is a negative.
    this.value = min;
    var delta = 0;

    this.update = function (input) {
         delta += (input - this.value) * ac;
         delta *= dr;
         this.value += delta;
         if (this.value < minV) {
             this.value = minV;
             if(delta < 0){
                 delta *= ref;
             }
         } else
         if (this.value > maxV) {
             this.value = maxV;
             if(delta > 0){
                 delta *= ref;
             }
         }
         return this.value;
     };
     // this move the value to the required value without any inertial or drag
     // is bound checked
     this.setValue = function (input) {
         delta = 0;
         this.value = Math.min(maxV, Math.min(minV, input));
         return this.value;
     }
 }

To use the code above

// in init
var rotater = new Inertia(0, Math.PI*2, 0.9, 0.4, -0.1);

// in the animation frame
myTriange = rotater.update(beta);

UPDATE

I have added some code to show the various settings and how they effect inertia. The code is not intended as an example of code style or DOM interfacing best practice, for it falls very far short on both counts. It uses the Inertia object I presented above. You can find that object at the top of the demo code.

The demo is best viewed Full Screen

//------------------------------------------------------
// Function from answer Inertia 
// Define a Inertia object. Set Answer for details.
// Has methods 
// update(input); Called once pre animation frame with input being the value to chase
// set(input); Hard sets the chasing value. Not drag or inertia
// Has properties
// value;  The chasing value bounds checked
function Inertia (min, max, acceleration, drag, reflect) {
    // some may question why the constants, why not use the closure on arguments
    // Reason: Some JS environments will fail to optimise code if the input
    //         arguments change. It may be tempting to extend this Object to 
    //         change the min, max or others. I put this here to highlight the
    //         fact that argument closure variables should not be modified
    //         if performance is important.
    const ac = acceleration;  // set constants
    const dr = drag;
    const minV = min;
    const maxV = max;
    const ref = -Math.abs(reflect); // ensure a negative. Why? because I always forget this is a negative.
    this.value = min;
    this.quiet = true;
    var delta = 0;

    this.update = function (input) {
         delta += (input - this.value) * ac;
         delta *= dr;
         this.value += delta;
         if (this.value < minV) {
             this.value = minV;
             if(delta < 0){
                delta *= ref;
             }
         } else
         if (this.value > maxV) {
             this.value = maxV;
             if(delta > 0){
                 delta *= ref;
             }
         }
         if(Math.abs(delta) < (maxV-minV)*0.001 && Math.abs(this.value-input) < 0.1 ){
             this.quiet = true;
         }else{
             this.quiet = false;
         }
         return this.value;
     };
     // this move the value to the required value without any inertial or drag
     // is bound checked
     this.setValue =  function (input) {
         delta = 0;
         this.quiet = true;
         this.value = Math.min(maxV, Math.max(minV, input));
         return this.value;
     }
 }
// End of answer
//--------------------------------------------------------







// All the following code is not part of the answer.
// I have not formatted, commented, and thoroughly tested it


/** MouseFullDemo.js begin **/
var canvasMouseCallBack = undefined;  // if needed
function createMouse(element){
    var demoMouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
            lx:0,ly:0,
            interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
            over : false,  // mouse is over the element
            bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
            getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
            startMouse:undefined,
        };
        function mouseMove(e) {
            //console.log(e)
            var t = e.type, m = mouse;
            m.lx = e.offsetX; m.ly = e.offsetY;
            m.x = e.clientX; m.y = e.clientY; 
            m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
            if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
            } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") {m.over = false;
            } else if (t === "mouseover") { m.over = true;
            } else if (t === "mousewheel") { m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
            if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); }
            e.preventDefault();
        }
        function startMouse(element){
            if(element === undefined){
                element = document;
            }
            "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach(
            function(n){element.addEventListener(n, mouseMove);});
            element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
        }
        mouse.mouseStart = startMouse;
        return mouse;
    })();
    demoMouse.mouseStart(element);
    return demoMouse;
}
/** MouseFullDemo.js end **/


var cellSize = 70;
var createImage=function(w,h){
    var i=document.createElement("canvas");
    i.width=w;
    i.height=h;
    i.ctx=i.getContext("2d");
    return i;
}
var drawCircle= function(img,x,y,r,col,colB,col1,width){
    var c = img.ctx;
    var g;
    c.lineWidth = width;
    c.strokeStyle = col1;
    g = c.createRadialGradient(x,y,1,x,y,r);
    g.addColorStop(0,col);
    g.addColorStop(1,colB);
    c.fillStyle = g;
    c.beginPath();
    c.arc(x,y,r-width*3,0,Math.PI*2);
    c.fill();

    c.strokeStyle = col1;
    c.fillStyle = col1;
    c.fillRect(x,y-width,r,width*2)
    c.fillStyle = col;
    c.fillRect(x+width,y-width/2,r,width)
}
var drawCircleO= function(img,x,y,r,col,colB,col1,width){
    var c = img.ctx;
    var g = c.createRadialGradient(x+r*0.21,y+r*0.21,r*0.7,x+r*0.21,y+r*0.21,r);
    g.addColorStop(0,"black");
    g.addColorStop(1,"rgba(0,0,0,0)");
    c.fillStyle = g;
    c.globalAlpha = 0.5;
    c.beginPath();
    c.arc(x+r*0.21,y+r*0.21,r,0,Math.PI*2);
    c.fill();
    c.globalAlpha = 1;
    var g = c.createRadialGradient(x*0.3,y*0.3,r*0.5,x*0.3,y*0.3,r);
    g.addColorStop(0,col);
    g.addColorStop(1,colB);
    c.lineWidth = width;
    c.strokeStyle = col1;
    c.fillStyle = g;
    c.beginPath();
    c.arc(x,y,r-width,0,Math.PI*2);
    c.stroke();
    c.fill();
}
// draws radial marks with miner markes 
// len,col and width are arrays
var drawCircleMarks= function(img,x,y,r,start,end,col,width,length,number,miner){
    var i,vx,vy,count,style,len;
    var c = img.ctx;
    var step = (end-start)/number;
    count = 0;

    end += step/2; // add to end to account for floating point rounding error
    for(i = start; i <= end; i+= step){
        vx = Math.cos(i);
        vy = Math.sin(i);
        if(count % miner === 0){
            style = 0;
        }else{
            style = 1;
        }
        c.strokeStyle = col[style];
        c.lineWidth = width[style];
        len = length[style];
        c.beginPath();
        c.moveTo(vx*r+x,vy*r+y);
        c.lineTo(vx*(r+len)+x,vy*(r+len)+y);
        c.stroke();
        count += 1;

    }
}

var defaultMap = {
    number:function(num,def){
        if( isNaN(num) ){
            return def
        }
        return Number(num);
    },
    colour:function(col,def){
        // no much code for demo so removed
        if(col === undefined || typeof col !== "string"){
            return def;
        }
        return col;
    },
    "ticks":{
        validate:function(val){
            return val===undefined?true:val?true:false;
        },
    },
    "type":{
        validate:function(val){
            switch (val) {
                case "dial":
                case "horizontal-slider":
                    return val;
            }
            return undefined
        }
    },
    "min":{
        validate:function(val){
            return defaultMap.number(val,0);
        }
    },
    "max":{
        validate:function(val){
            return defaultMap.number(val,100);
        }
    },
    "drag":{
        validate:function(val){
            return defaultMap.number(val,0.4);
        }
    },    
    "reflect":{
        validate:function(val){
            return defaultMap.number(val,0.2);
        }
    },
    "accel":{
        validate:function(val){
            return defaultMap.number(val,0.4);
        }
    },
    "value":{
        validate:function(val){
            return defaultMap.number(val,0);
        }
    },
    "tick-color":{
        validate:function(val){
            
        }
    },
    "decimals":{
        validate:function(val){
            return defaultMap.number(val,0);
        }
    },
    "display":{
        validate:function(val){
            if(val === null || val === undefined || typeof val !== "string"){
                return undefined;
            }
            return document.querySelector(val);
        }
    }
}

// validates user defined DOM attribute
function getSafeAttribute(element,name){
    var val,def;
    if(name === undefined){
        return undefined;
    }
    def = defaultMap[name];
    if(def === undefined){ // unknown attribute 
        if(element.attributes["data-"+name]){
            return element.attributes["data-"+name].value;
        }
        return undefined
    }
    if(element.attributes["data-"+name]){
        val = element.attributes["data-"+name].value;
    }
    return def.validate(val);
}
// Custom user control
// Warning this can return a undefined control
function Control(element,owner){
    var dialUpdate,drawFunc,w,h,nob,back,mouse,minSize,canvas,chaser,dragging,dragX,dragY,dragV,realValue,startP,endP,lastVal,reflect,drag,accel;
    var dialUpdate = function(){
        var unitPos = (this.value-this.min)/(this.max-this.min);
        canvas.ctx.setTransform(1,0,0,1,0,0);
        canvas.ctx.clearRect(0,0,w,h);
        canvas.ctx.drawImage(back,0,0);
        canvas.ctx.setTransform(1,0,0,1,back.width/2,back.height/2);
        canvas.ctx.rotate(unitPos *(endP-startP)+startP);
        canvas.ctx.drawImage(nob,-nob.width/2,-nob.height/2);
    }

    if(element === undefined){ // To my UNI mentor with love.. LOL
         return undefined; 
    }
    this.type = getSafeAttribute(element,"type");
    if(this.type === undefined){
        return undefined;     // this is a non standared contrutor return
    }
    
    this.owner = owner; // expose owner
    // exposed properties
    this.min = getSafeAttribute(element,"min");
    this.max = getSafeAttribute(element,"max");
    this.ticks = getSafeAttribute(element,"ticks");
    this.tickColor = getSafeAttribute(element,"tick-color");
    this.value = realValue = getSafeAttribute(element,"value");

    this.display = getSafeAttribute(element,"display");
    if(this.display){
        var decimals  = getSafeAttribute(element,"decimals");
    }
    drag = getSafeAttribute(element,"drag");
    accel = getSafeAttribute(element,"accel");  
    reflect = getSafeAttribute(element,"reflect");;
    chaser = new Inertia(this.min,this.max,accel,drag,reflect);
    
    w = element.offsetWidth;
    h = element.offsetHeight;

    canvas = createImage(w,h);
    minSize = Math.min(w,h);
    mouse = createMouse(element);
    if(this.type === "dial"){
        nob = createImage(minSize*(3/4),minSize*(3/4));
        drawCircle(nob,minSize*(3/4)*(1/2),minSize*(3/4)*(1/2),minSize*(3/4)*(1/2),"white","#CCC","black",3);
        back = createImage(minSize,minSize);
        startP = Math.PI*(3/4);
        endP = Math.PI*(9/4);
        drawCircleMarks(
            back,
            minSize/2,
            minSize/2,
            minSize/3,
            startP,
            endP,
            ["black","#666"],
            [2,1],
            [minSize*(1/4),minSize*(1/9)],
            16,
            4
        );
        drawCircleO(back,minSize*(1/2),minSize*(1/2),minSize*(3/4)*(1/2),"white","#aaa","black",3);        
        drawFunc = dialUpdate.bind(this);
    }
    element.appendChild(canvas);
    this.active = true;
    this.resetChaser = function(min,max,accel1,drag1,reflect1){
        this.min = min===null?this.min:min;
        this.max = max===null?this.max:max;
        drag = drag1===null?drag:drag1;
        accel = accel1===null?accel:accel1;
        reflect = reflect1===null?reflect:reflect1;
        chaser = new Inertia(this.min,this.max,accel,drag,reflect);
        
        chaser.setValue(this.value);
        drawFunc();
    }
    this.update = function(){
        var inVal;
        if(mouse.over){
            element.style.cursor = "drag_ew";
        }
        if((this.owner.mouse.buttonRaw&1) === 1 && !dragging && mouse.over && this.owner.draggingID === -1){
            dragX = this.owner.mouse.x - (mouse.lx-w/2);
            dragY = this.owner.mouse.y - (mouse.ly-h/2);
            dragging = true;
            this.owner.draggingID = this.ID;
        }else
        if(this.owner.draggingID === this.ID && ((this.owner.mouse.buttonRaw&1) === 1 || (this.owner.mouse.buttonRaw&1) === 0) && dragging){
            inVal = (Math.atan2(this.owner.mouse.y-dragY,this.owner.mouse.x-dragX)+Math.PI*2);
            if(inVal > Math.PI*0.5+Math.PI*2){
                 inVal -= Math.PI*2;
            }
            realValue = inVal;
            realValue = ((realValue-startP)/(endP-startP))*(this.max-this.min)+this.min;
            if((this.owner.mouse.buttonRaw&1) === 0){
                dragging = false;  
                this.owner.draggingID = -1;
            }
        }
        realValue = Math.min(this.max,Math.max(this.min,realValue));
        this.value = chaser.update(realValue);
        
        if(!chaser.quiet){
            drawFunc();
            if(this.display){
                this.display.textContent = realValue.toFixed(decimals);
            }
            if(this.onchange !== undefined && typeof this.onchange === "function"){
                this.onchange({value:realValue,target:element,control:this});
            }
        }
    }
    // force chaser to wake up
    chaser.setValue(this.value);
    drawFunc();
    element.control = this;
}

// find and create controllers
function Controllers(name){
    var controls, elems, i, control, e;
    var ID = 0;
    controls = [];
    elems = document.querySelectorAll("."+name);
    for(i = 0; i < elems.length; i++){
        e = elems[i];
        control = new Control(e,this);
        control.ID = ID++;
        if(control !== undefined){
            controls.push(control);
        }
    }
    this.update = function(){
        controls.forEach(function(cont){
            cont.update();
        })
    }
    this.mouse = createMouse(document);
    this.draggingID = -1;
}

// get elements to play with the large control
var c = new Controllers("testControl");
var drag = document.getElementById("dragSetting");
var accel = document.getElementById("accelSetting");
var reflect = document.getElementById("reflectSetting");
var bigDial = document.getElementById("bigDial");
var bigDialt = document.getElementById("bigDialText");
// callback for large controller
function changeBigDial(e){
    bigDial.control.resetChaser(null,null,drag.control.value,accel.control.value,reflect.control.value);
    if(accel.control.value === 0 || drag.control.value === 0){
        var str = "Can no move as Drag and/or Acceleration is Zero";
    }else{
        var str  = "A:"+ accel.control.value.toFixed(3);
        str += "D:"+ drag.control.value.toFixed(3);
        str += "R:-"+ reflect.control.value.toFixed(3);
    }
    bigDialt.textContent = str;
}
// set callbacks
drag.control.onchange = changeBigDial;
accel.control.onchange = changeBigDial;
reflect.control.onchange = changeBigDial;

// Update all controls
function update(){
    c.update();
    requestAnimationFrame(update);
}
update();

.testControl {
    width:110px;
    height:110px;
    display: inline-block;
    text-align:center;
}
.big {
    width:200px;
    height:200px;
    
}
.demo {
    text-align:center;
}

<div class="demo"><h3> Examples of variouse Drag and Acceleration settings</h3>
<p>Click on the control to change the setting. Click drag to adjust setting. The first two rows are preset. <b>D</b> and <b>A</b> above the control are the <b>D</b>rag and <b>A</b>cceleration settings for the control under it.</p>
<span class="testControl" data-drag="0.1" data-accel="0.9" data-value="0" data-type = "dial" ><b>D</b>:0.1 <b>A</b>:0.9</span>
<span class="testControl" data-drag="0.2" data-accel="0.8" data-value="0" data-type = "dial" ><b>D</b>:0.2 <b>A</b>:0.8</span>
<span class="testControl" data-drag="0.3" data-accel="0.7" data-value="0" data-type = "dial" ><b>D</b>:0.3 <b>A</b>:0.7</span>
<span class="testControl" data-drag="0.4" data-accel="0.6" data-value="0" data-type = "dial" ><b>D</b>:0.4 <b>A</b>:0.6</span>
<span class="testControl" data-drag="0.5" data-accel="0.5" data-value="0" data-type = "dial" ><b>D</b>:0.5 <b>A</b>:0.5</span>
<span class="testControl" data-drag="0.6" data-accel="0.4" data-value="0" data-type = "dial" ><b>D</b>:0.6 <b>A</b>:0.4</span>
<span class="testControl" data-drag="0.7" data-accel="0.3" data-value="0" data-type = "dial" ><b>D</b>:0.7 <b>A</b>:0.3</span>
<span class="testControl" data-drag="0.8" data-accel="0.2" data-value="0" data-type = "dial" ><b>D</b>:0.8 <b>A</b>:0.2</span>
<span class="testControl" data-drag="0.9" data-accel="0.1" data-value="0" data-type = "dial" ><b>D</b>:0.9 <b>A</b>:0.1</span><br>
<span class="testControl" data-drag="0.9" data-accel="0.9" data-value="0" data-type = "dial" ><b>D</b>:0.9 <b>A</b>:0.9</span>
<span class="testControl" data-drag="0.8" data-accel="0.8" data-value="0" data-type = "dial" ><b>D</b>:0.8 <b>A</b>:0.8</span>
<span class="testControl" data-drag="0.7" data-accel="0.7" data-value="0" data-type = "dial" ><b>D</b>:0.7 <b>A</b>:0.7</span>
<span class="testControl" data-drag="0.6" data-accel="0.6" data-value="0" data-type = "dial" ><b>D</b>:0.6 <b>A</b>:0.6</span>
<span class="testControl" data-drag="0.5" data-accel="0.5" data-value="0" data-type = "dial" ><b>D</b>:0.5 <b>A</b>:0.5</span>
<span class="testControl" data-drag="0.4" data-accel="0.4" data-value="0" data-type = "dial" ><b>D</b>:0.4 <b>A</b>:0.4</span>
<span class="testControl" data-drag="0.3" data-accel="0.3" data-value="0" data-type = "dial" ><b>D</b>:0.3 <b>A</b>:0.3</span>
<span class="testControl" data-drag="0.2" data-accel="0.2" data-value="0" data-type = "dial" ><b>D</b>:0.2 <b>A</b>:0.2</span>
<span class="testControl" data-drag="0.1" data-accel="0.1" data-value="0" data-type = "dial" ><b>D</b>:0.1 <b>A</b>:0.1</span><br>
<h3>The following 3 dials control the inertia setting of the large dial</h3>
<span class="testControl" id="dragSetting" data-value="0" data-display="#display-drag" data-decimals="3" data-min="0" data-max="1"  data-type = "dial" >Drag <span id="display-drag">0.000</span></span>
<span class="testControl" id="accelSetting" data-value="0" data-display="#display-accel" data-decimals="3" data-min="0" data-max="1" data-type = "dial" >Accel <span id="display-accel">0.000</span></span>
<span class="testControl" id="reflectSetting" data-value="0" data-display="#display-reflect" data-decimals="3" data-min="0" data-max="1" data-type = "dial" >Reflect <span id="display-reflect">0.000</span></span>
</div>
<div class="demo">
<div class="testControl big" id="bigDial"  data-drag="0.1" data-accel="0.1" data-value="0" data-type = "dial" ><span id="bigDialText">Letf click drag to change</span></div><br>
</div>

Hope this helps.

这篇关于如何添加惯性跟踪鼠标移动的动画?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆