jjzjj

javascript - 如何为跟踪鼠标移动的动画添加惯性?

coder 2024-07-28 原文

我正在尝试使用 mouseMove 事件围绕原点旋转三 Angular 形。
我正在使用 touchstarttouchmove 事件获取触摸起点当前触摸点,然后我' m 使用以下方法轻松找到方向 Angular :

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

然后我在 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;

}

我正在使用 RequestAnimationFrame 循环管理我的绘画。

问题是动画不稳定,我需要旋转惯性。如何修复此功能?

最佳答案

惯性、加速度和阻力

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

一步一步

定义必需的值

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

阻力是一个大于0且<=1的值,在功能上更像是一个阻尼 spring="" 。小于="" 0.5="" 的值可以平滑停止计数值。值="">= 0.5 会导致追逐值在所需值附近反弹,随着拖动值向 1 移动,反弹变得越来越明显。

加速度值是一个大于 0 且 <= 1="">

阻力和加速度相互影响。 对于

  • 快速光响应 accel = 0.9, drag = 0.49
  • 轻慢响应 accel = 0.1, drag = 0.49
  • 重度慢响应 accel = 0.02, drag = 0.49
  • 硬弹性响应 accel = 0.7drag = 0.7
  • 缓慢的弹性响应 accel = 0.1drag = 0.7

正在更新

然后每帧一次,通过添加到deltaV向输入值加速,

rotateDelta += (rotate - rotateChase) * rotateAcceleration;

通过减小三 Angular 洲的幅度来增加阻力。

rotateDelta *= rotateDrag;

然后将deltaV加到追逐值上,

rotateChase += rotateDelta;

现在可以显示追逐值了

myTriangle.rotation = rotateChase;

添加边界

那是一个不受约束的追逐者。设置值的界限需要一些额外的代码。首先通过设置最小值和最大值来描述边界。

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

然后通过定义反射或反弹来描述值越界时的行为。 0 使它在边界处停止,< 0="" 和="">= -1 会从末端产生小的反弹。其他值(value)创造有趣的 FX

var rotateReflect = -0.5;  

然后是控制该行为的代码

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;
   }
}

显示和使用

现在您只需选择将哪个值用作控件的输入。 rotateChaserotate 都可以使用,但是 rotateChase 可能需要一点时间才能适应。我所做的是从 rotateChase 值中获取舍入值,该值删除了追踪的精细细节。

例如,如果控件用于音量

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

让它变得简单

为了同一个值(value),所有接缝都像是大量工作。但我们是程序员,天生懒惰,所以让我们将其划分为一个简单的惯性类

// 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;
     }
 }

使用上面的代码

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

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

更新

我添加了一些代码来展示各种设置以及它们如何影响惯性。该代码并非旨在作为代码风格或 DOM 接口(interface)最佳实践的示例,因为它在这两个方面都远远不够。它使用我上面介绍的 Inertia 对象。您可以在演示代码的顶部找到该对象。

最好在 全屏 中观看该演示

//------------------------------------------------------
// 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>

希望这对您有所帮助。

关于javascript - 如何为跟踪鼠标移动的动画添加惯性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34374774/

有关javascript - 如何为跟踪鼠标移动的动画添加惯性?的更多相关文章

  1. ruby - 我需要将 Bundler 本身添加到 Gemfile 中吗? - 2

    当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/

  2. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  3. ruby - 将 Bootstrap Less 添加到 Sinatra - 2

    我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它

  4. ruby - 如何为 emacs 安装 ruby​​-mode - 2

    我刚刚为fedora安装了emacs。我想用emacs编写ruby。为ruby​​提供代码提示、代码完成类型功能所需的工具、扩展是什么? 最佳答案 ruby-mode已经包含在Emacs23之后的版本中。不过,它也可以通过ELPA获得。您可能感兴趣的其他一些事情是集成RVM、feature-mode(Cucumber)、rspec-mode、ruby-electric、inf-ruby、rinari(用于Rails)等。这是我当前用于Ruby开发的Emacs配置:https://github.com/citizen428/emacs

  5. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  6. ruby-on-rails - 如何重命名或移动 Rails 的 README_FOR_APP - 2

    当我在我的Rails应用程序根目录中运行rakedoc:app时,API文档是使用/doc/README_FOR_APP作为主页生成的。我想向该文件添加.rdoc扩展名,以便它在GitHub上正确呈现。更好的是,我想将它移动到应用程序根目录(/README.rdoc)。有没有办法通过修改包含的rake/rdoctask任务在我的Rakefile中执行此操作?是否有某个地方可以查找可以修改的主页文件的名称?还是我必须编写一个新的Rake任务?额外的问题:Rails应用程序的两个单独文件/README和/doc/README_FOR_APP背后的逻辑是什么?为什么不只有一个?

  7. ruby - 可以通过多少种方法将方法添加到 ruby​​ 对象? - 2

    当谈到运行时自省(introspection)和动态代码生成时,我认为ruby​​没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby​​的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资

  8. ruby - 如何在 Ruby 中向现有方法定义添加语句 - 2

    我注意到类定义,如果我打开classMyClass,并在不覆盖的情况下添加一些东西我仍然得到了之前定义的原始方法。添加的新语句扩充了现有语句。但是对于方法定义,我仍然想要与类定义相同的行为,但是当我打开defmy_method时似乎,def中的现有语句和end被覆盖了,我需要重写一遍。那么有什么方法可以使方法定义的行为与定义相同,类似于super,但不一定是子类? 最佳答案 我想您正在寻找alias_method:classAalias_method:old_func,:funcdeffuncold_func#similartoca

  9. ruby-on-rails - 添加回形针新样式不影响旧上传的图像 - 2

    我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司

  10. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

随机推荐