用于交互式弹性线的Canvas框架?

时间:2016-03-31 13:39:58

标签: javascript html5 canvas

我想为我的网页创建一个交互式弹性线,当用户将线条悬停时,它将像弹性效果或橡皮筋拉伸效果一样生动,鼠标将使对象恢复原始形状。

演示对象

enter image description here

我是HTML5 Canvas中的新手,我希望它能用JavaScript画布库完成,但是当我搜索最好的画布库时,我会得到更多选项,所以我对哪一个选择达到目标感到困惑我的目标。 ThreeJsfabricJsPaperJs等是受欢迎的画布库。

我想建议哪个框架最适合我的目标。

感谢并感谢您的帮助心态。

2 个答案:

答案 0 :(得分:2)

您需要使用Inverse Kinematic,或者更具体地说是Kinematic Chain

有许多方法或多或少复杂。这是一个简单的方法,可以让你拖动终点,其余的将跟随。它并不完美,但可能会为此目的而做。

(反向)运动链

主要功能如下。它假定定义了带点的数组以及distance变量:

// calculate IK chain (from last to first)
function calc() {

    var angle, i, p1, p2;

    for(i = points.length - 1; i > 0; i--) {
        p1 = points[i];                                // current point
        p2 = points[i-1];                              // previous point
        angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);  // calc angle

        p2.x = p1.x + distance * Math.cos(angle);      // update previous point
        p2.y = p1.y + distance * Math.sin(angle);      // based on a fixed distance
    }
}

请注意,distance变量设置为固定长度,这是此处的关键。

现在我们需要做的就是检测鼠标拖动链中的最后一点,其余的将跟随。

链在行动



var c = document.querySelector("canvas"),
	ctx = c.getContext("2d"),
    
    // the chain - dragged by the *last* point
	points = [
		{x: 50, y: 50},
		{x: 100, y: 60},
		{x: 90, y: 90},
		{x: 120, y: 110},
		{x: 200, y: 80},
		{x: 250, y: 130}
	],
	distance = 50,
	isDown = false;

// set canvas size
resize();
window.onresize = resize;

function resize() {
  c.width = window.innerWidth;
  c.height = window.innerHeight;
  calc();
  render()
}

// handle mouse
c.onmousedown = function(e) {
  var pos = getXY(e), 
      p = points[points.length - 1];

  isDown = (pos.x > p.x - 7 && pos.x < p.x + 7 && pos.y > p.y - 7 && pos.y < p.y + 7);
};

window.onmousemove = function(e) {
  if (!isDown) return;
	
  points[points.length - 1] = getXY(e);    // override last point with mouse position

  // update chain and canvas
  calc();
  render();	
};

window.onmouseup = function() {isDown = false};

// adjusted mouse position
function getXY(e) {
	var rect = c.getBoundingClientRect();
	return {
		x: e.clientX - rect.left,
		y: e.clientY - rect.top
	}
}

// IK chain calculations
function calc() {
	
	var angle, i, p1, p2;
	
	for(i = points.length - 1; i > 0; i--) {
		p1 = points[i];
		p2 = points[i-1];
		angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
		
		p2.x = p1.x + distance * Math.cos(angle);
		p2.y = p1.y + distance * Math.sin(angle);
	}
}

// render line and handle
function render() {

	var lp, radius = 7;
	
	ctx.clearRect(0, 0, c.width, c.height);
	
	// render current chain
	ctx.beginPath();
	ctx.moveTo(points[0].x, points[0].y);
	for(var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
	ctx.lineWidth = 3;
	ctx.strokeStyle = "#07f";
	ctx.stroke();
	
	lp = points[points.length - 1];
	
	// draw handle
	ctx.beginPath();
	ctx.moveTo(lp.x + radius, lp.y);
	ctx.arc(lp.x, lp.y, radius, 0, Math.PI*2);
	ctx.lineWidth = 2;
	ctx.strokeStyle = "#900";
	ctx.stroke();
}
&#13;
<canvas></canvas>
&#13;
&#13;
&#13;

回到根

为了让它反弹,你需要原始坐标,然后插入链中的相应点。

这当然会发生在鼠标注册事件上。如果您愿意,可以使用缓动功能;在这种情况下,缓和可能是最合适的。

反弹

这个例子并不打算解决整个问题,也没有优化,但是,你应该能够得到所需要的要点。根据需要进行修改。

为此起作用:

  • 渲染函数现在接受一个参数,因此我们可以将它提供给任何点数组
  • 我们需要在固定点(原始路径)和IK链之间进行插值。为此,我们使用临时数组。
  • 我们动画,而t是[0,1]
  • 完成后,我们将IK点重置为原始点并重新计算/渲染它。
  • 我还为链段添加了min / max,以展示如何让链条更具弹性。

&#13;
&#13;
var c = document.querySelector("canvas"),
    ctx = c.getContext("2d"),
    
    // the fixed point chain
    pointsFixed = [
      {x: 50, y: 50},
      {x: 100, y: 60},
      {x: 90, y: 90},
      {x: 120, y: 110},
      {x: 200, y: 80},
      {x: 250, y: 130}
    ],

    // for the IK chain - dragged by the *last* point
    points = [
      {x: 50, y: 50},
      {x: 100, y: 60},
      {x: 90, y: 90},
      {x: 120, y: 110},
      {x: 200, y: 80},
      {x: 250, y: 130}
	],
    
    min = 40, max = 70,
    isDown = false,
    
    // for animation
    isPlaying = false,
    t, step = 0.1;       // t = [0, 1]

// set canvas size
resize();
window.onresize = resize;

function resize() {
  c.width = window.innerWidth;
  c.height = window.innerHeight;
  calc();
  render(points)
}

// handle mouse
c.onmousedown = function(e) {
  var pos = getXY(e), 
      p = points[points.length - 1];

  isDown = (pos.x > p.x - 7 && pos.x < p.x + 7 && pos.y > p.y - 7 && pos.y < p.y + 7);
};

window.onmousemove = function(e) {
  if (!isDown) return;
	
  points[points.length - 1] = getXY(e);    // override last point with mouse position

  // update chain and canvas
  calc();
  render(points);	
};

window.onmouseup = function() {
  if (isDown) {
    isDown = false;
    t = 0;                // reset t for new animation
    isPlaying = true;     // allow looping
    animate();            // start the animation
  }
};

// adjusted mouse position
function getXY(e) {
  var rect = c.getBoundingClientRect();
  return {
    x: e.clientX - rect.left,
    y: e.clientY - rect.top
  }
}

// IK chain calculations
function calc() {
	
  var angle, i, p1, p2, dx, dy, distance;
	
  for(i = points.length - 1; i > 0; i--) {
    p1 = points[i];
    p2 = points[i-1];
    dx = p2.x - p1.x;
    dy = p2.y - p1.y;
    angle = Math.atan2(dy, dx);
    distance = Math.max(min, Math.min(max, Math.sqrt(dx*dx + dy*dy)));
		
    p2.x = p1.x + distance * Math.cos(angle);
    p2.y = p1.y + distance * Math.sin(angle);
  }
}

// interpolate and animate
function animate() {
  if (isPlaying) {
    
    // create a temp. array with interpolated points
    for(var i = 0, p, pts = []; i < points.length; i++) {
      pts.push(lerp(points[i], pointsFixed[i], t*t));  // t*t for easing
    }
    
    // increase t in animation
    t += step;
    
    // keep animating?
    if (t <= 1) {
      render(pts);
      requestAnimationFrame(animate)
    }
    else {
      // we're done
      isPlaying = false;
      points = pts;
      calc();
      render(points);
    } 
  }
  
  function lerp(p1, p2, t) {
    return {
      x: p1.x + (p2.x - p1.x) * t,
      y: p1.y + (p2.y - p1.y) * t
    }
  }
}

// render line and handle
function render(points) {

  var lp, radius = 7;
	
  ctx.clearRect(0, 0, c.width, c.height);
	
	// render current chain
  ctx.beginPath();
  ctx.moveTo(points[0].x, points[0].y);
  for(var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
  ctx.lineWidth = 3;
  ctx.strokeStyle = "#07f";
  ctx.stroke();
	
  lp = points[points.length - 1];
	
  // draw handle
  ctx.beginPath();
  ctx.moveTo(lp.x + radius, lp.y);
  ctx.arc(lp.x, lp.y, radius, 0, Math.PI*2);
  ctx.lineWidth = 2;
  ctx.strokeStyle = "#900";
  ctx.stroke();
}
&#13;
<canvas></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

您正在寻找的技术是poly line simplification

你的问题与偏离主题有关,因为它要求提供图书馆推荐。但是,没有一个库可以像您描述的那样自动动画您的演示路径。

所以我觉得说...

没什么坏处

您可以定义和绘制&#34; wiggly&#34;路径作为一组连接点(折线)。然后,当您的用户离开路径时,您可以使用路径简化算法来移除多点,直到路径&#34;拉直&#34;。

Ramer-Douglas-Peucker algorithm是一种有用的路径简化算法。

Here's an example路径简化行动。在演示中,向右移动鼠标以简化路径并向左移动以显示更复杂的路径(复杂= =路径上的更多点)。