Как повернуть объект холста после перемещения мыши с помощью замедления?

Я не уверен, что использовал правильное слово здесь. Я предполагаю, что ослабление означает, что это не следует за мышью сразу, но с некоторой задержкой?

В данный момент радужная оболочка вращается в направлении моей мыши. Что если я хочу, чтобы это имело тот же эффект, что иэтот?. Это очень сложно сделать или просто требовать простых изменений кода? Существует ли стандартный способ / решение для такого рода проблем?

Вот мой текущий код. Это также можно найти наВращающаяся Ирис .

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
    
class Circle {
    constructor(options) {
      this.cx = options.x;
      this.cy = options.y;
      this.radius = options.radius;
      this.color = options.color;

      this.angle = options.angle;

      this.binding();
    }
      
    binding() {
      const self = this;
      window.addEventListener('mousemove', (e) => {
        self.calculateAngle(e);
      });
    }
      
    calculateAngle(e) {
      if (!e) return;
      let rect = canvas.getBoundingClientRect(),
          vx = e.clientX - this.cx,
          vy = e.clientY - this.cy;
      this.angle = Math.atan2(vy, vx);

    }
      
    renderEye() {
      ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

      ctx.rotate(this.angle);

      let eyeRadius = this.radius / 3;

      ctx.beginPath();
      ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
      ctx.fill();

    }
    
    render() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.beginPath();
      ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
      ctx.closePath();
      ctx.strokeStyle = '#09f';
      ctx.lineWidth = 1;
      ctx.stroke();

      this.renderMessage();
      this.renderEye();

    }
    
    renderMessage() {
      ctx.font = "18px serif";
      ctx.strokeStyle = 'black';
      ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
    }
}
    
var rotatingCircle = new Circle({
    x: 320,
  y: 160,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
    rotatingCircle.render();
    requestAnimationFrame(animate);
}

animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

Обновлено с возможным решением:

Я на самом деле перешел по ссылке, которую я разместил в вопросе, и использую аналогичный способ, чтобы облегчить ротацию, что, по-моему, похоже на то, что в категориях @ Blindman67 было недетерминированным.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

class Circle {
	constructor(options) {
  	this.cx = options.x;
    this.cy = options.y;
    this.radius = options.radius;
    this.color = options.color;
    this.toAngle = 0;
    this.angle = options.angle;
    this.velocity = 0;
    this.maxAccel = 0.04;
    this.binding();
  }
  
  binding() {
  	const self = this;
  	window.addEventListener('mousemove', (e) => {
      self.calculateAngle(e);
    });
  }
  
  calculateAngle(e) {
    if (!e) return;
    let rect = canvas.getBoundingClientRect(),
        // mx = parseInt(e.clientX - rect.left),
        // my = parseInt(e.clientY - rect.top),
        vx = e.clientX - this.cx,
        vy = e.clientY - this.cy;
  	this.toAngle = Math.atan2(vy, vx);

  }
  
  clip(x, min, max) {
    return x < min ? min : x > max ? max : x;
  }

  renderEye() {
    ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

    let radDiff = 0;
    if (this.toAngle != undefined) {
       radDiff = this.toAngle - this.angle;
    }

    if (radDiff > Math.PI) {
      this.angle += 2 * Math.PI;
    } else if (radDiff < -Math.PI) {
      this.angle -= 2 * Math.PI;
    }

    let easing = 0.06;
    let targetVel = radDiff * easing;
    this.velocity = this.clip(targetVel, this.velocity - this.maxAccel, this.velocity + this.maxAccel);
    this.angle += this.velocity;

    ctx.rotate(this.angle);
        
    let eyeRadius = this.radius / 3;

    ctx.beginPath();
    ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
    ctx.fill();

  }

  render() {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  	ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.beginPath();
    ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
    ctx.closePath();
    ctx.strokeStyle = '#09f';
    ctx.lineWidth = 1;
    ctx.stroke();
   
    this.renderMessage();
    this.renderEye();
    
  }

  renderMessage() {
    ctx.font = "18px serif";
    ctx.strokeStyle = 'black';
    ctx.fillText('Angle: ' + this.angle.toFixed(3), 30, canvas.height - 40);
    ctx.fillText('toAngle: ' + this.toAngle.toFixed(3), 30, canvas.height - 20);
  }
}

var rotatingCircle = new Circle({
	x: 250,
  y: 130,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
	rotatingCircle.render();
	requestAnimationFrame(animate);
}

animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

Ответы на вопрос(2)

Есть много способов сделать смягчение. Два метода, которые я опишу вкратце: детерминированное ослабление и (на удивление) недетерминированное. Разница заключается в том, что назначение легкости либо известно (определено), либо неизвестно (в ожидании большего ввода пользователя)

Детерминированное ослабление.

Для этого у вас есть начальное значение и конечное значение. То, что вы хотите сделать, это найти позицию между двумя на основе некоторого значения времени. Это означает, что начальные и конечные значения также должны быть связаны со временем.

Например

var startVal = 10;
var startTime = 100;
var endVal = 100;
var endTime = 200;

Вы захотите найти значение в 150 раз на полпути между ними. Для этого вы конвертируете время в дробь, где время 100 (начало) возвращает 0, а время 200 (конец) возвращает 1, мы называем это нормализованное время. Затем вы можете умножить разницу между начальным и конечным значениями на эту дробь, чтобы найти смещение.

Таким образом, для значения времени 150, чтобы получить значение (theValue), мы делаем следующее.

var time = 150;
var timeDif = endTime - startTime
var fraction = (startTime - time) / timeDif; // the normalised time
var valueDif = endVal - startVal;
var valueOffset = valueDif * fraction;
var theValue = startVal + valueOffset;

или более кратким.

// nt is normalised time
var nt = (startTime - time) / (endTime - startTime)
var theValue = startVal + (endVal - startVal) * nt;

Теперь, чтобы применить ослабление, нам нужно изменить нормализованное время. Функция замедления просто принимает значение от 0 до 1 включительно и изменяет его. Таким образом, если вы введете 0,25, функция замедления вернет 0,1 или 0,5 вернет 0,5, а 0,75 вернет 0,9. Как видите, модификация меняет скорость изменения с течением времени.

Пример функции замедления.

var easeInOut = function (n, pow) {
    n = Math.min(1, Math.max(0, n)); // clamp n
    var nn = Math.pow( n, pow);
    return (nn / ( nn + Math.pow(1 - n, pow)))
}

Эта функция принимает два входа: дробь n (от 0 до 1 включительно) и мощность. Сила определяет величину ослабления. Если pow = 1, то нет ослабления, и функция возвращает n. Если pow = 2, то функция такая же, как и функция облегчения ввода CSS, запускается медленно, ускоряется, а затем замедляется в конце. если pow <1 и pow> 0, то начало замедления быстро замедляется на полпути, а затем ускоряется до конца.

Чтобы использовать функцию замедления в приведенном выше примере значения замедления

// nt is normalised time
var nt = (startTime - time) / (endTime - startTime);
nt = easeInOut(nt,2); // start slow speed up, end slow
var theValue = startVal + (endVal - startVal) * nt;

Вот как делается детерминированное смягчение

Отличная функция ослабления страницыЛегкие примеры и код и другую страницу длябыстрое визуальное смягчение референции

Недетерминированное ослабление

Вы можете не знать, каков конечный результат функции замедления, поскольку в любой момент она может измениться из-за нового пользовательского ввода, если вы используете вышеописанные методы и изменяете конечное значение в середине того, насколько легко результат будет противоречивым и уродливым. Если вы когда-либо делали исчисление, вы можете признать, что вышеприведенная функция замедления является полиномом и, таким образом, является антипроизводной более простой функции. Эта функция просто определяет количество изменений за шаг по времени. Таким образом, для недетерминированного решения все, что мы знаем, это изменение для следующего временного шага. Для облегчения работы (начинайте быстро и медленно по мере приближения к пункту назначения) мы сохраняем значение, представляющее текущую скорость (скорость изменения), и изменяем эту скорость по мере необходимости.

const ACCELERATION_COEFFICIENT = 0.3;
const DRAG_COEFFICIENT = 0.99;
var currentVal = 100;
var destinationVal = 200;
var currentSpeed = 0;

Затем для каждого временного шага вы делаете следующее

var accel = destinationVal - currentVal;  // get the acceleration
accel *= ACCELERATION_COEFFICIENT; // modify it so we are not there instantly
currentSpeed += accel; // add that to the speed
currentSpeed *= DRAG_COEFFICIET; // add some drag to further ease the function as it approaches destination
currentVal += currentSpeed; // add the speed to the current value

Теперь currentVal будет соответствовать значению пункта назначения, если пункт назначения изменяется, чем скорость изменения (скорость) также изменяется согласованным образом. CurrentVal может никогда не попасть в пункт назначения, если пункт назначения постоянно меняется, однако, если пункт назначения прекратит изменять, текущий val приблизится и в конечном итоге остановится в пункте назначения (под остановкой я подразумеваю, что скорость станет настолько малой, что будет бессмысленной)

Поведение этих методов очень зависит от двух коэффициентов, поэтому игра с этими значениями будет варьировать ослабление. Некоторые значения приведут вас к переизбытку с небольшим колебанием, другие будут очень медленными, как движение через патоку.

Вы также можете сделать его намного более сложным, добавив вторую скорость изменения, таким образом, вы можете получить ускорение ускорения, которое будет имитировать такие вещи, как сопротивление воздуха, которое изменяет ускорение во времени. Вы также можете добавить максимумы к скорости изменения, чтобы установить ограничения скорости.

Это должно помочь вам расслабиться.

Больше информации Для получения дополнительной информации см. Эти ответыКак бы я оживил а такжеКак масштабировать между двумя точками

Недетерминированное смягчение применяется к вашему примеру

Я добавил замедление к вашей функции, но оно ввело новую проблему, которая будет возникать при использовании циклических значений, таких как угол. Поскольку в этом ответе я не буду вдаваться в подробности, вы можете найти решение этой проблемы вНахождение наименьшего угла.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ACCELERATION_COEFFICIENT = 0.15;
const DRAG_COEFFICIENT = 0.5;        
class Circle {
    constructor(options) {
      this.cx = options.x;
      this.cy = options.y;
      this.radius = options.radius;
      this.color = options.color;

      this.angle = options.angle;
      this.angleSpeed = 0;
      this.currentAngle = this.angle;

      this.binding();
    }
      
    binding() {
      const self = this;
      window.addEventListener('mousemove', (e) => {
        self.calculateAngle(e);
      });
    }
      
    calculateAngle(e) {
      if (!e) return;
      let rect = canvas.getBoundingClientRect(),
          vx = e.clientX - this.cx,
          vy = e.clientY - this.cy;
      this.angle = Math.atan2(vy, vx);

    }
      
    renderEye() {
      // this should be in a separate function 
      this.angleSpeed += (this.angle - this.currentAngle) * ACCELERATION_COEFFICIENT;
      this.angleSpeed *= DRAG_COEFFICIENT;
      this.currentAngle += this.angleSpeed;


      ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);

      ctx.rotate(this.currentAngle);

      let eyeRadius = this.radius / 3;

      ctx.beginPath();
      ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
      ctx.fill();

    }
    
    render() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.beginPath();
      ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
      ctx.closePath();
      ctx.strokeStyle = '#09f';
      ctx.lineWidth = 1;
      ctx.stroke();

      this.renderMessage();
   
      this.renderEye();

    }
    
    renderMessage() {
      ctx.font = "18px serif";
      ctx.strokeStyle = 'black';
      ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
    }
}
    
var rotatingCircle = new Circle({
    x: 320,
  y: 160,
  radius: 40,
  color: 'black',
  angle: Math.random() * Math.PI * 2
});

function animate() {
    rotatingCircle.render();
    requestAnimationFrame(animate);
}

animate();
<canvas id='canvas' style='width: 700; height: 500;'></canvas>

 Blindman6722 июн. 2016 г., 18:55
@newguy Да, это (ваше решение) является очень хорошим решением проблемы.
 newguy22 июн. 2016 г., 18:46
Оба ваших метода очень полезны. Я многому научился. Однако в этом примере наблюдается сбой под углом около -2,9, при котором радужная оболочка очень быстро отскакивает к дну. Я обновил свой вопрос с возможным решением, которое было вдохновлено ссылкой, которую я разместил в вопросе. Можете ли вы посмотреть, если это хорошо?
 Blindman6722 июн. 2016 г., 18:59
Вы также можете найти угол между двумя векторами, получивMath.asin(crossProduct(v1,v2)) где v1 - текущий вектор из центра глаза, а v2 - предыдущий вектор. (обратите внимание, векторы должны быть нормализованы). Это даст вам относительное изменение угла, а не абсолютный угол. перекрестный продуктv1.x * v2.y - v1.y * v2.x

Насколько я знаю, с h5 canvas вам, возможно, придется самостоятельно писать функции easy. Тем не менее, анимация CSS3 имеет несколько встроенных функций облегчения, вы можете написать.foo {transition: bottom 1s ease} и когда.foo элементыbottom изменения свойств стиля, они будут двигаться со скоростью, определеннойease функция. Увидеть:https://developer.mozilla.org/en-US/docs/Web/CSS/transition https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions .

Кроме того, посмотрите на эти удивительные анимированные BB-8 (созданные с использованием CSS-анимации):http://codepen.io/mdixondesigns/pen/PPEJwz http://codepen.io/Chyngyz/pen/YWwYGq http://codepen.io/bullerb/pen/gMpxNZ

Ваш ответ на вопрос