javascript - HTML Canvas 和 Javascript - 通过选择(从多个位置)触发音频

标签 javascript html canvas html5-canvas

我的 HTML Canvas 中有一个选择菜单,我想触发相应的音频文件。我尝试通过在 makeSelection 函数的 if (this.hovered)(this.clicked) 部分声明图像来实现此目的SelectionForMenu 原型(prototype),这样在每个新选择上,所选音频文件都会被重新定义,但这会导致加载缓慢和音频重叠等问题。这也是有问题的,因为我试图让屏幕底部的扬声器按钮也播放与当前选择相对应的音频,所以如果它仅在该函数中定义,则 makeButton 函数。

您可以在下面的代码片段中看到选择菜单和扬声器按钮。菜单中的每个新选项都应该播放与其对应的音频文件一次(我无法将其添加到此演示中)。可以通过重新单击选择或单击扬声器按钮来重播它,但每次单击只能引发一次音频播放,当然重叠是不希望的。任何帮助将不胜感激。

var c=document.getElementById('game'),
		canvasX=c.offsetLeft,
		canvasY=c.offsetTop,
		ctx=c.getContext('2d');

var button = function(id, x, strokeColor) {
	this.id = id;
	this.x = x;
	this.strokeColor = strokeColor;
	this.hovered = false;
	this.clicked = false;
}

button.prototype.makeInteractiveButton = function() {
	if (this.hovered) {
		if (this.clicked) {
			this.fillColor = '#DFBCDE';
		} else {
			this.fillColor = '#CA92C8'
		}
	} else {
		this.fillColor = '#BC77BA'
	}
	ctx.strokeStyle=this.strokeColor;
	ctx.fillStyle=this.fillColor;
	ctx.beginPath();
	ctx.lineWidth='5';
	ctx.arc(this.x, 475, 20, 0, 2*Math.PI);
	ctx.closePath();
	ctx.stroke();
	ctx.fill();
}

button.prototype.hitTest = function(x, y) {
	return (Math.pow(x-this.x, 2) + Math.pow(y-475, 2) < Math.pow(20, 2));
}

var selectionForMenu = function(id, text, y) {
	this.id = id;
	this.text = text;
	this.y = y;
	this.hovered = false;
	this.clicked = false;
	this.lastClicked = false;
}

selectionForMenu.prototype.makeSelection = function() {
	var fillColor='#A84FA5';
	if (this.hovered) {
		if (this.clicked) {
			if (this.lastClicked) {
				fillColor='#E4C7E2';
			} else {
				fillColor='#D5A9D3';
			}
		} else if (this.lastClicked) {
			fillColor='#D3A4D0';
		} else {
			fillColor='#BA74B7';
		}
	} else if (this.lastClicked) {
		fillColor='#C78DC5';
	} else {
		fillColor='#A84FA5';
	}
	ctx.beginPath();
	ctx.fillStyle=fillColor;
	ctx.fillRect(0, this.y, 350, 30)
	ctx.stroke();

	ctx.font='10px Noto Sans';
	ctx.fillStyle='white';
	ctx.textAlign='left';
	ctx.fillText(this.text, 10, this.y+19);
}

selectionForMenu.prototype.hitTest = function(x, y) {
	return (x >= 0) && (x <= (350)) && (y >= this.y) && (y <= (this.y+30)) && !((x >= 0) && (y > 450));
}

var Paint = function(element) {
	this.element = element;
	this.shapes = [];
}

Paint.prototype.addShape = function(shape) {
	this.shapes.push(shape);
}

Paint.prototype.render = function() {
	ctx.clearRect(0, 0, this.element.width, this.element.height);

	for (var i=0; i<this.shapes.length; i++) {
		try {
			this.shapes[i].makeSelection();
		}
		catch(err) {}
	}

	ctx.beginPath();
	ctx.fillStyle='#BC77BA';
	ctx.fillRect(0, 450, 750, 50);
	ctx.stroke();

	for (var i=0; i<this.shapes.length; i++) {
		try {
			this.shapes[i].makeInteractiveButton();
		}
		catch(err) {}
	}

	var speaker = new Image(25, 25);
	speaker.src='/image/lXg2I.png';
	ctx.drawImage(speaker, 162.5, 462.5);
}

Paint.prototype.setHovered = function(shape) {
	for (var i=0; i<this.shapes.length; i++) {
		this.shapes[i].hovered = this.shapes[i] == shape;
	}
	this.render();
}

Paint.prototype.setClicked = function(shape) {
	for (var i=0; i<this.shapes.length; i++) {
		this.shapes[i].clicked = this.shapes[i] == shape;
	}
	this.render();
}

Paint.prototype.setUnclicked = function(shape) {
	for (var i=0; i<this.shapes.length; i++) {
		this.shapes[i].clicked = false;
		if (Number.isInteger(this.shapes[i].id)) {
			this.shapes[i].lastClicked = this.shapes[i] == shape;
		}
	}
	this.render();
}

Paint.prototype.select = function(x, y) {
	for (var i=this.shapes.length-1; i >= 0; i--) {
		if (this.shapes[i].hitTest(x, y)) {
			return this.shapes[i];
		}
	}
	return null
}

var paint = new Paint(c);
var btn = new button('speaker', 175, '#FFFCF8');
var selection = [];
for (i=0; i<15; i++) {
	selection.push(new selectionForMenu(i+1, i, i*30));
}

paint.addShape(btn);
for (i=0; i<15; i++) {
	paint.addShape(selection[i])
}

paint.render();

function mouseDown(event) {
	var x = event.x - canvasX;
	var y = event.y - canvasY;
	var shape = paint.select(x, y);

	paint.setClicked(shape);
}

function mouseUp(event) {
	var x = event.x - canvasX;
	var y = event.y - canvasY;
	var shape = paint.select(x, y);

	paint.setUnclicked(shape);
}

function mouseMove(event) {
	var x = event.x - canvasX;
	var y = event.y - canvasY;
	var shape = paint.select(x, y);

	paint.setHovered(shape);
}

c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
  z-index: -1;
  margin: 1em auto;
  border: 1px solid black;
  display: block;
  background: #9F3A9B;
}

img {
  z-index: 0;
  position: absolute;
  pointer-events: none;
}

#speaker {
  top: 480px;
  left: 592px;
}

#snail {
  top: 475px;
  left: 637.5px;
}
<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>uTalk Demo</title>
	<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
</head>
<body>
	<canvas id="game" width = "350" height = "500"></canvas>
  <script type='text/javascript' src='wordpractice copy.js'></script>
</body>
</html>

最佳答案

当您想要音频响应能力时,请忘记 MediaElements,而使用 Web Audio API。 MediaElements( <audio><video> )很慢,并且 http 缓存是一场噩梦。

使用 Web Audio API,您可以首先将所有媒体下载为 arrayBuffer,将其音频数据解码到 AudioBuffer,然后将其附加到 js 对象。 从那里,您将能够在几微秒内播放这些媒体的新实例。

请注意,以下 ES6 语法,对于较旧的浏览器,here is an ES5 rewrite另请注意,Internet Explorer < Edge 支持 Web Audio API,如果您需要支持这些浏览器,则必须使用音频元素进行回退。

(function myFirstDrumKit() {

  const db_url = 'https://dl.dropboxusercontent.com/s/'; // all our medias are stored on dropbox

  // we'll need to first load all the audios
  function initAudios() {
    const promises = drum.parts.map(part => {
      return fetch(db_url + part.audio_src) // fetch the file
        .then(resp => resp.arrayBuffer()) // as an arrayBuffer
        .then(buf => drum.a_ctx.decodeAudioData(buf)) // then decode its audio data
        .then(AudioBuf => {
          part.buf = AudioBuf; // store the audioBuffer (won't change)
          return Promise.resolve(part); // done
        });
    });
    return Promise.all(promises); // when all are loaded
  }

  function initImages() {
    // in this version we have only an static image,
    // but we could have multiple per parts, with the same logic as for audios
    var img = new Image();
    img.src = db_url + drum.bg_src;
    drum.bg = img;
    return new Promise((res, rej) => {
      img.onload = res;
      img.onerror = rej;
    });
  }

  let general_solo = false;
  let part_solo = false;

  const drum = {
    a_ctx: new AudioContext(),
    generate_sound: (part) => {
      // called each time we need to play a source
      const source = drum.a_ctx.createBufferSource();
      source.buffer = part.buf;
      source.connect(drum.gain);
      // to keep only one playing at a time
      // simply store this sourceNode, and stop the previous one
      if(general_solo){
        // stop all playing sources
        drum.parts.forEach(p => (p.source && p.source.stop(0)));
        }
      else if (part_solo && part.source) {
        // stop only the one of this part
        part.source.stop(0);
      }
      // store the source
      part.source = source;
      source.start(0);
    },
    parts: [{
        name: 'hihat',
        x: 90,
        y: 116,
        w: 160,
        h: 70,
        audio_src: 'kbgd2jm7ezk3u3x/hihat.mp3'
      },
      {
        name: 'snare',
        x: 79,
        y: 192,
        w: 113,
        h: 58,
        audio_src: 'h2j6vm17r07jf03/snare.mp3'
      },
      {
        name: 'kick',
        x: 80,
        y: 250,
        w: 200,
        h: 230,
        audio_src: '1cdwpm3gca9mlo0/kick.mp3'
      },
      {
        name: 'tom',
        x: 290,
        y: 210,
        w: 110,
        h: 80,
        audio_src: 'h8pvqqol3ovyle8/tom.mp3'
      }
    ],
    bg_src: '0jkaeoxls18n3y5/_drumkit.jpg?dl=0',
  };
  drum.gain = drum.a_ctx.createGain();
  drum.gain.gain.value = .5;
  drum.gain.connect(drum.a_ctx.destination);


  function initCanvas() {
    const c = drum.canvas = document.createElement('canvas');
    const ctx = drum.ctx = c.getContext('2d');
    c.width = drum.bg.width;
    c.height = drum.bg.height;
    ctx.drawImage(drum.bg, 0, 0);
    document.body.appendChild(c);
    addEvents(c);
  }

  const isHover = (x, y) =>
    (drum.parts.filter(p => (p.x < x && p.x + p.w > x && p.y < y && p.y + p.h > y))[0] || false);


  function addEvents(canvas) {
    let mouse_hovered = false;
    canvas.addEventListener('mousemove', e => {
      mouse_hovered = isHover(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop)
      if (mouse_hovered) {
        canvas.style.cursor = 'pointer';
      } else {
        canvas.style.cursor = 'default';
      }
    })
    canvas.addEventListener('mousedown', e => {
      e.preventDefault();
      if (mouse_hovered) {
        drum.generate_sound(mouse_hovered);
      }
    });
    const checkboxes = document.querySelectorAll('input');
    checkboxes[0].onchange = function() {
      general_solo = this.checked;
      general_solo && (checkboxes[1].checked = part_solo = true);
    };
    checkboxes[1].onchange = function() {
      part_solo = this.checked;
      !part_solo && (checkboxes[0].checked = general_solo = false);
    };
  }
  Promise.all([initAudios(), initImages()])
    .then(initCanvas);

})()

/* 
Audio Samples are from https://sampleswap.org/filebrowser-new.php?d=DRUMS+%28FULL+KITS%29%2FSpasm+Kit%2F
Original image is from http://truimg.toysrus.co.uk/product/images/UK/0023095_CF0001.jpg?resize=500:500
*/
<label>general solo<input type="checkbox"></label><br>
<label>part solo<input type="checkbox"></label><br>

关于javascript - HTML Canvas 和 Javascript - 通过选择(从多个位置)触发音频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44282474/

相关文章:

Java、Canvas下透明JFrame

javascript - 如何通过ajax发送base64图像

javascript - 两个变量之间的区别,第一个变量被分配了一个未定义的值,第二个变量仅声明为未初始化的 var

javascript - 使 SVG 响应式缩放

Javascript 按名称更改字段值

jquery - 登录后导航中的下拉菜单

javascript - 3 维空间中的轨道倾 Angular

javascript - 如何将 "draw signature"添加到忍者表单

javascript - 如何选中和取消选中复选框以及如何使复选框单击行外单击

javascript - 如何将字符串一次转换为数学函数?