使用 Canvas 作为多范围输入的填充

Use Canvas as pad for multiple range inputs

我正在尝试构建一个 html canvas 垫,允许用户在垫上拖放一个点,然后 return 两个值(一个用于 Y 轴,一个用于 Y 轴),我可以使用它来触发使用网络音频的效果 API。

我已经解决了问题的网络音频 API 部分。

用户:

到目前为止,我已经能够创建和呈现 canvas 和圆圈,并在 svg 元素和 window 上添加事件侦听器。我的想法是,我可以检测到 canvas 中何时发生事件以及该单击事件何时离开 canvas.

// Draw SVG pad
function drawDelayPad() {
var canvas = document.getElementById('delayPad');
if (canvas.getContext) {
  var ctx = canvas.getContext('2d');
  var rectangle = new Path2D();
  rectangle.rect(1, 1, 200, 200);

  var circle = new Path2D();
  circle.moveTo(150, 150);
  circle.arc(100, 35, 10, 0 , 2 * Math.PI);
  ctx.stroke(rectangle);
  ctx.fill(circle);
  }
}

// Listener on canvas
var canvas = document.getElementById('delayPad');

canvas.addEventListener("mousedown", function(){
  console.log("click inside our canvas")
})

// Listener on document to check if we're outside the canvas
window.addEventListener("mouseup", function(){
  console.log("outside our canvas")
});

所以我觉得我现在需要确定的是当点击事件确实发生在canvas的内部时,它离圆圈有多远,是否落在圆圈的范围内,只要 mousedown 事件处于活动状态,我就应该重新绘制它。

如有任何帮助,我们将不胜感激。

我找到了一个不错的小解决方案,它证实了我对点击计数器的怀疑!所有功劳都归功于 rectangleWorld,因为我大部分时间只能修改他们提供的示例。

Here's a codepen

// Draw SVG pad
function canvasApp(canvasID) {
  var theCanvas = document.getElementById(canvasID);
  var context = theCanvas.getContext("2d");

  init();

  var numShapes;
  var shapes;
  var dragIndex;
  var dragging;
  var mouseX;
  var mouseY;
  var dragHoldX;
  var dragHoldY;

  function init() {
    numShapes = 1;
    shapes = [];

    makeShapes();

    drawScreen();

    theCanvas.addEventListener("mousedown", mouseDownListener, false);
  }

  function makeShapes() {
    var i;
    var tempX;
    var tempY;
    var tempRad;
    var tempR;
    var tempG;
    var tempB;
    var tempColor;
    var tempShape;
    for (i = 0; i < numShapes; i++) {
      // My canvas element is 240x240
      tempRad = 10;
      tempX = 0 + tempRad;
      tempY = 240 - tempRad;
      tempR = Math.floor(Math.random() * 255);
      tempG = Math.floor(Math.random() * 255);
      tempB = Math.floor(Math.random() * 255);
      tempColor = "rgb(" + tempR + "," + tempG + "," + tempB + ")";
      tempShape = {
        x: tempX,
        y: tempY,
        rad: tempRad,
        color: tempColor
      };
      shapes.push(tempShape);
    }
  }

  function mouseDownListener(evt) {
    var i;
    //We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
    //only the topmost one will be dragged.
    var highestIndex = -1;

    //getting mouse position correctly, being mindful of resizing that may have occured in the browser:
    var bRect = theCanvas.getBoundingClientRect();
    mouseX = (evt.clientX - bRect.left) * (theCanvas.width / bRect.width);
    mouseY = (evt.clientY - bRect.top) * (theCanvas.height / bRect.height);

    //find which shape was clicked
    for (i = 0; i < numShapes; i++) {
      if (hitTest(shapes[i], mouseX, mouseY)) {
        dragging = true;
        if (i > highestIndex) {
          //We will pay attention to the point on the object where the mouse is "holding" the object:
          dragHoldX = mouseX - shapes[i].x;
          dragHoldY = mouseY - shapes[i].y;
          highestIndex = i;
          dragIndex = i;
        }
      }
    }

    if (dragging) {
      window.addEventListener("mousemove", mouseMoveListener, false);
    }
    theCanvas.removeEventListener("mousedown", mouseDownListener, false);
    window.addEventListener("mouseup", mouseUpListener, false);

    //code below prevents the mouse down from having an effect on the main browser window:
    if (evt.preventDefault) {
      evt.preventDefault();
    } //standard
    else if (evt.returnValue) {
      evt.returnValue = false;
    } //older IE
    return false;
  }

  function mouseUpListener(evt) {
    theCanvas.addEventListener("mousedown", mouseDownListener, false);
    window.removeEventListener("mouseup", mouseUpListener, false);
    if (dragging) {
      dragging = false;
      window.removeEventListener("mousemove", mouseMoveListener, false);
    }
  }

  function mouseMoveListener(evt) {
    var posX;
    var posY;
    var shapeRad = shapes[dragIndex].rad;
    var minX = shapeRad;
    var maxX = theCanvas.width - shapeRad;
    var minY = shapeRad;
    var maxY = theCanvas.height - shapeRad;
    //getting mouse position correctly
    var bRect = theCanvas.getBoundingClientRect();
    mouseX = (evt.clientX - bRect.left) * (theCanvas.width / bRect.width);
    mouseY = (evt.clientY - bRect.top) * (theCanvas.height / bRect.height);

    // Divide by width of canvas and multiply to get percentage out of 100
    var DelayTime = ((mouseX / 240) * 100);
    // Invert returned value to get percentage out of 100
    var DelayFeedback = (100 - (mouseY / 240) * 100);

    // Set delay time as a portion of 2seconds
    delayEffect.delayTime.value = DelayTime / 100 * 2.0;
    // set delay feedback gain as value of random number
    delayFeedback.gain.value = (DelayFeedback / 100 * 1.0);

    //clamp x and y positions to prevent object from dragging outside of canvas
    posX = mouseX - dragHoldX;
    posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
    posY = mouseY - dragHoldY;
    posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);

    shapes[dragIndex].x = posX;
    shapes[dragIndex].y = posY;

    drawScreen();
  }

  function hitTest(shape, mx, my) {

    var dx;
    var dy;
    dx = mx - shape.x;
    dy = my - shape.y;

    //a "hit" will be registered if the distance away from the center is less than the radius of the circular object
    return (dx * dx + dy * dy < shape.rad * shape.rad);
  }

  function drawShapes() {
    var i;
    for (i = 0; i < numShapes; i++) {
      context.fillStyle = shapes[i].color;
      context.beginPath();
      context.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2 * Math.PI, false);
      context.closePath();
      context.fill();
    }
  }

  function drawScreen() {
    context.fillStyle = "#000000";
    context.fillRect(0, 0, theCanvas.width, theCanvas.height);

    drawShapes();
  }

}

window.addEventListener("load", windowLoadHandler, false);

function windowLoadHandler() {
  canvasApp('delayPad');
}

还有一些缺点,比如mouseMoveListener,虽然限制了圆圈的移动,但是会继续增加你的x&y值。这意味着您必须使用现有的侦听器来检查拖动事件何时退出圆圈,或者更简单地说,您可以为 X 和 Y 值设置上限。

您必须创建一个对象来存储您的 xy 值。
在下面的示例中,我将其称为 pad.

此对象将为您的 canvas 可视化和音频处理服务。
这些都是 outputs(分别是视觉和音频),而 input 将是用户手势(例如 mousemove)。

输入更新pad对象,同时输出读取它。

[注意]:此示例仅适用于最新的 Chrome 和 Firefox,因为它使用尚未广泛实施的 MediaElement.captureStream()

const viz_out = canvas.getContext('2d');
let aud_out, mainVolume;

// our pad object holding the coordinates
const pad = {
  x: 0,
  y: 0,
  down: false,
  rad: 10
};

let canvRect = canvas.getBoundingClientRect();

function mousemove(event) {
  if (!aud_out || !pad.down) {
    return;
  }
  pad.x = event.clientX - canvRect.left;
  pad.y = canvRect.height - (event.clientY - canvRect.top); // inverts y axis
  // all actions are splitted
  updateViz();
  updateAud();
  updateLog();
}

viz_out.setTransform(1, 0, 0, -1, 0, 300) // invert y axis on the canvas too
// simply draws a circle where at our pad's coords
function updateViz() {
  viz_out.clearRect(0, 0, canvas.width, canvas.height);
  viz_out.beginPath();
  viz_out.arc(pad.x, pad.y, pad.rad, 0, Math.PI * 2);
  viz_out.fill();
}
// You'll do it as you wish, here it just modifies a biquadFilter
function updateAud() {
  const default_freq = 350;
  const max_freq = 6000;
  const y_ratio = pad.y / 300;
  aud_out.frequency.value = (default_freq + (max_freq * y_ratio)) - default_freq;
  aud_out.Q.value = (pad.x / 300) * 10;
  mainVolume.value = 1 + ((pad.y + pad.x) / 75);
}

function updateLog() {
  log.textContent = `x:${~~pad.x} y:${~~pad.y}`;
}
canvas.addEventListener('mousedown', e => pad.down = true);
canvas.addEventListener('mouseup', e => pad.down = false);
canvas.addEventListener('mousemove', mousemove);


btn.onclick = e => {
  btn.textContent = 'stop';
  startLoadingAudio();
  btn.onclick = e => {
    mainVolume.value = 0;
  }
}

window.onscroll = window.onresize = e => canvRect = canvas.getBoundingClientRect();

function startLoadingAudio() {
  const audio = new Audio();
  audio.loop = true;
  audio.muted = true;
  audio.onloadedmetadata = e => {
    audio.play();
    const stream = audio.captureStream ? audio.captureStream() : audio.mozCaptureStream();
    initAudioProcessor(stream);
    updateLog();
    window.onscroll();
    updateViz();
  }
  // FF will "taint" the stream, even if the media is served with correct CORS...
  fetch("https://dl.dropboxusercontent.com/s/8c9m92u1euqnkaz/GershwinWhiteman-RhapsodyInBluePart1.mp3").then(resp => resp.blob()).then(b => audio.src = URL.createObjectURL(b));

  function initAudioProcessor(stream) {
    var a_ctx = new AudioContext();
    var gainNode = a_ctx.createGain();
    var biquadFilter = a_ctx.createBiquadFilter();
    var source = a_ctx.createMediaStreamSource(stream);
    source.connect(biquadFilter);
    biquadFilter.connect(gainNode);
    gainNode.connect(a_ctx.destination);
    aud_out = biquadFilter;
    mainVolume = gainNode.gain;
    biquadFilter.type = "bandpass";
  }
}
canvas {
  border: 1px solid;
}
<button id="btn">
start
</button>
<pre id="log"></pre>
<canvas id="canvas" width="300" height="300"></canvas>