I'm developing a web using Angular CLI.
I'm trying to applying similar things like this :
https://codepen.io/chrisdoble/pen/WQLLVp
Is there any way to write plain javascript in component.ts.
Here is my JS :
var image = document.getElementById('hero-bg');
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = imageCanvas.getContext('2d');
var lineCanvas = document.createElement('canvas');
var lineCanvasContext = lineCanvas.getContext('2d');
var pointLifetime = 1000;
var points = [];
var newImage = document.getElementById('hero')
if (image.complete) {
start();
} else {
image.onload = start;
}
function start() {
document.addEventListener('mousemove', onMouseMove);
window.addEventListener('resize', resizeCanvases);
newImage.appendChild(imageCanvas);
resizeCanvases();
tick();
}
function onMouseMove(event) {
points.push({
time: Date.now(),
x: event.clientX,
y: event.clientY
});
}
function resizeCanvases() {
imageCanvas.width = lineCanvas.width = window.innerWidth;
imageCanvas.height = lineCanvas.height = window.innerHeight;
}
function tick() {
// Remove old points
points = points.filter(function(point) {
var age = Date.now() - point.time;
return age < pointLifetime;
});
drawLineCanvas();
drawImageCanvas();
requestAnimationFrame(tick);
}
function drawLineCanvas() {
var minimumLineWidth = 25;
var maximumLineWidth = 100;
var lineWidthRange = maximumLineWidth - minimumLineWidth;
var maximumSpeed = 50;
lineCanvasContext.clearRect(0, 0, lineCanvas.width, lineCanvas.height);
lineCanvasContext.lineCap = 'round';
lineCanvasContext.shadowBlur = 30;
lineCanvasContext.shadowColor = '#000';
for (var i = 1; i < points.length; i++) {
var point = points[i];
var previousPoint = points[i - 1];
// Change line width based on speed
var distance = getDistanceBetween(point, previousPoint);
var speed = Math.max(0, Math.min(maximumSpeed, distance));
var percentageLineWidth = (maximumSpeed - speed) / maximumSpeed;
lineCanvasContext.lineWidth = minimumLineWidth + percentageLineWidth * lineWidthRange;
// Fade points as they age
var age = Date.now() - point.time;
var opacity = (pointLifetime - age) / pointLifetime;
lineCanvasContext.strokeStyle = 'rgba(0, 0, 0, ' + opacity + ')';
lineCanvasContext.beginPath();
lineCanvasContext.moveTo(previousPoint.x, previousPoint.y);
lineCanvasContext.lineTo(point.x, point.y);
lineCanvasContext.stroke();
}
}
function getDistanceBetween(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function drawImageCanvas() {
// Emulate background-size: cover
var width = imageCanvas.width;
var height = imageCanvas.width / image.naturalWidth * image.naturalHeight;
if (height < imageCanvas.height) {
width = imageCanvas.height / image.naturalHeight * image.naturalWidth;
height = imageCanvas.height;
}
imageCanvasContext.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
imageCanvasContext.globalCompositeOperation = 'source-over';
imageCanvasContext.drawImage(image, 0, 0, width, height);
imageCanvasContext.globalCompositeOperation = 'destination-in';
imageCanvasContext.drawImage(lineCanvas, 0, 0);
}
And here is my Component
import { Component } from '#angular/core';
import * as $ from 'jquery';
#Component({
selector: 'app-hero',
templateUrl: './hero.component.html',
styleUrls: ['./hero.component.scss']
})
export class HeroComponent {
}
The Error i got is this :
message: 'Property 'complete' does not exist on type 'HTMLElement'.'
message: 'Property 'naturalWidth' does not exist on type 'HTMLElement'.'
message: 'Property 'naturalHeight' does not exist on type 'HTMLElement'.'
message: 'Argument of type 'HTMLElement' is not assignable to parameter of type 'HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ImageBitmap'.
Type 'HTMLElement' is not assignable to type 'ImageBitmap'.
Property 'width' is missing in type 'HTMLElement'.'
Everything works fine if i put the code inline at Index.html file,
but i don't want to put it there, i want to apply it in my component.
Please, help me to find a way to implement this plain Javascript in my component.ts.
Or maybe if there are any Typescript master here, kindly recode for me. hehehe
Thank You guys alot.
You do not have to use jQuery. Angular have all things in place to interact with a dom.
You have to use https://angular.io/api/core/Renderer2 and https://angular.io/api/core/HostListener to adjust the code the way angular can understand that.
I just converted the code from https://codepen.io/chrisdoble/pen/WQLLVp in to angular way. Here is an example what are you looking for:
export class NativeBlurComponent implements OnInit {
img = new Image();
imageCanvas = document.createElement('canvas');
imageCanvasContext = this.imageCanvas.getContext('2d');
lineCanvas = document.createElement('canvas');
lineCanvasContext = this.lineCanvas.getContext('2d');
pointLifetime = 1000;
points = [];
constructor(private renderer: Renderer2) { }
ngOnInit() {
this.img.src = document.querySelector('img').src;
if (this.img.complete) {
this.start();
} else {
this.img.onload = this.start;
}
}
start() {
this.renderer.appendChild(document.body, this.imageCanvas);
this.resizeCanvases();
this.tick();
}
#HostListener('document:mousemove', [ '$event' ])
onMouseMove(event) {
this.points.push({
time: Date.now(),
x: event.clientX,
y: event.clientY
});
}
#HostListener('window:resize', [ '$event' ])
resizeCanvases() {
this.imageCanvas.width = this.lineCanvas.width = window.innerWidth;
this.imageCanvas.height = this.lineCanvas.height = window.innerHeight;
}
tick() {
// Remove old points
this.points = this.points.filter(function(point) {
const age = Date.now() - point.time;
return age < this.pointLifetime;
});
this.drawLineCanvas();
this.drawImageCanvas();
requestAnimationFrame(this.tick);
}
drawLineCanvas() {
const minimumLineWidth = 25;
const maximumLineWidth = 100;
const lineWidthRange = maximumLineWidth - minimumLineWidth;
const maximumSpeed = 50;
this.lineCanvasContext.clearRect(0, 0, this.lineCanvas.width, this.lineCanvas.height);
this.lineCanvasContext.lineCap = 'round';
this.lineCanvasContext.shadowBlur = 30;
this.lineCanvasContext.shadowColor = '#000';
for (let i = 1; i < this.points.length; i++) {
const point = this.points[i];
const previousPoint = this.points[i - 1];
// Change line width based on speed
const distance = this.getDistanceBetween(point, previousPoint);
const speed = Math.max(0, Math.min(maximumSpeed, distance));
const percentageLineWidth = (maximumSpeed - speed) / maximumSpeed;
this.lineCanvasContext.lineWidth = minimumLineWidth + percentageLineWidth * lineWidthRange;
// Fade points as they age
const age = Date.now() - point.time;
const opacity = (this.pointLifetime - age) / this.pointLifetime;
this.lineCanvasContext.strokeStyle = 'rgba(0, 0, 0, ' + opacity + ')';
this.lineCanvasContext.beginPath();
this.lineCanvasContext.moveTo(previousPoint.x, previousPoint.y);
this.lineCanvasContext.lineTo(point.x, point.y);
this.lineCanvasContext.stroke();
}
}
getDistanceBetween(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
drawImageCanvas() {
// Emulate background-size: cover
let width = this.imageCanvas.width;
let height = this.imageCanvas.width / this.img.naturalWidth * this.img.naturalHeight;
if (height < this.imageCanvas.height) {
width = this.imageCanvas.height / this.img.naturalHeight * this.img.naturalWidth;
height = this.imageCanvas.height;
}
this.imageCanvasContext.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height);
this.imageCanvasContext.globalCompositeOperation = 'source-over';
this.imageCanvasContext.drawImage(this.img, 0, 0, width, height);
this.imageCanvasContext.globalCompositeOperation = 'destination-in';
this.imageCanvasContext.drawImage(this.lineCanvas, 0, 0);
}
}
Of course you can.
But you will have to handle with:
ElementRef : https://angular.io/api/core/ElementRef
Renderer : https://angular.io/api/core/Renderer
or Renderer 2.
Typescript need you to give the type of the element for HTMLElement.
for example:
svg: SVGElement = ...
In your case, you have to say that your element has the type: HTMLImageElement.
Related
I'm making a small recording feature using the user/browser microphone. When the microphone is getting sound an audio visualization is shown (like an equalizer). So fare so good.
But i really want to change the way the visualization looks to something like the image below. But i have never worked with this area before and don't know how to go about it.
I imagine something like this:
https://images.app.goo.gl/pfKgnGnQz3MJVkbW6
I have two questions:
Is it possible to get a result like the attached?
How do you get started on something like that? (or has anyone done something like this that can share examples?)
My current code for the equlizer visualization
audioContext = new AudioContext();
gumStream = stream;
input = audioContext.createMediaStreamSource(stream);
rec = new Recorder(input,{numChannels:1})
rec.record()
inputPoint = audioContext.createGain();
audioInput = input;
audioInput.connect(inputPoint);
analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 1024;
inputPoint.connect( analyserNode );
updateAnalysers();
function updateAnalysers(time) {
if (!analyserContext) {
var canvas = document.getElementById("analyser");
canvasWidth = canvas.width;
canvasHeight = canvas.height;
analyserContext = canvas.getContext('2d');
}
{
var SPACING = 5;
var BAR_WIDTH = 5;
var numBars = Math.round(canvasWidth / SPACING);
var freqByteData = new Uint8Array(analyserNode.frequencyBinCount);
analyserNode.getByteFrequencyData(freqByteData);
analyserContext.clearRect(0, 0, canvasWidth, canvasHeight);
analyserContext.fillStyle = '#D5E9EB';
analyserContext.lineCap = 'round';
var multiplier = analyserNode.frequencyBinCount / numBars;
// Draw rectangle for each frequency bin.
for (var i = 0; i < numBars; ++i) {
var magnitude = 0;
var offset = Math.floor( i * multiplier );
for (var j = 0; j< multiplier; j++)
magnitude += freqByteData[offset + j];
magnitude = magnitude / multiplier;
var magnitude2 = freqByteData[i * multiplier];
analyserContext.fillRect(i * SPACING, canvasHeight, BAR_WIDTH, -magnitude);
}
}
rafID = window.requestAnimationFrame( updateAnalysers );
}
Ans 1 :
Your image is broken so can't answer but as far as I know, you can visualize any waveform using audio data
How do you get started on something like that? (or has anyone done something like this that can share examples?)
Ans 2:
So I did use the customized waveform. I am sharing my code
import React, { Component } from "react";
import AudioVisualiser from "./AudioVisualiser";
class AudioAnalyser extends Component {
constructor(props) {
super(props);
this.state = { audioData: new Uint8Array(0) };
this.tick = this.tick.bind(this);
}
componentDidMount() {
this.audioContext = new (window.AudioContext ||
window.webkitAudioContext)();
this.analyser = this.audioContext.createAnalyser();
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
this.source = this.audioContext.createMediaStreamSource(this.props.audio);
this.source.connect(this.analyser);
this.rafId = requestAnimationFrame(this.tick);
}
tick() {
this.analyser.getByteTimeDomainData(this.dataArray);
this.setState({ audioData: this.dataArray });
this.rafId = requestAnimationFrame(this.tick);
}
componentWillUnmount() {
cancelAnimationFrame(this.rafId);
// this.analyser.disconnect();
// this.source.disconnect();
}
render() {
return <AudioVisualiser audioData={this.state.audioData} />;
}
}
export default AudioAnalyser;
import React, { Component } from 'react';
class AudioVisualiser extends Component {
constructor(props) {
super(props);
this.canvas = React.createRef();
}
componentDidUpdate() {
this.draw();
}
draw() {
const { audioData } = this.props;
const canvas = this.canvas.current;
const height = canvas.height;
const width = canvas.width;
const context = canvas.getContext('2d');
let x = 0;
const sliceWidth = (width * 1.0) / audioData.length;
context.lineWidth = 2;
context.strokeStyle = '#000000';
context.clearRect(0, 0, width, height);
context.beginPath();
context.moveTo(0, height / 2);
for (const item of audioData) {
const y = (item / 255.0) * height;
context.lineTo(x, y);
x += sliceWidth;
}
context.lineTo(x, height / 2);
context.stroke();
}
render() {
return <canvas width="300" height="300" ref={this.canvas} />;
}
}
export default AudioVisualiser;
I have made my own Audio Player with JavaScript using the p5.js library. The script loops through all elements of the webpage with onload() and adds a canvas (The thing that displays the graphics) object to all html elements of class audioPlayer. With Firefox it all works fine and it can play an audio file from a specified file path. When i load my website inside of Chrome or Edge, I get the following error message in the console:
Not allowed to load local resource: blob:null/59e0d5f9-cf73-4135-9595-3214e6cc964e
This error message occurs multiple times, every time with another string after the blop:null/. There is also the error message:
The AudioContext was not allowed to start. It must be resumed (or created) after a user gesture on
the page. https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#webaudio
I believe the issue is that JavaScript doesn't like loading in local files because of safety and stuff. But if that's the case, why does it works on Firefox? Is it an unsafe browser?
Also, if the Audio has to be loaded in with the help of user interaction, is there a way to get around that? Like I said, on Firefox, it's no problem.
I should probably add that the website is not online, it's running locally and that I'm using Firefox Developer Edition.
Also, if there are any p5.js users out there, I used the function windowResized() to make the size of the canvas adapt to the browser window. Again, it works fine using Firefox, but in Chrome and Edge it throws an error because I'm using variables of a class that I haven't created before (even though I create them inside of function setup() which should run before windowResized() and - again - it works fine on Firefox.
Here's the code of the JavaScript file:
$(document).ready(function () {
let audioPanel = function (p) {
let track;
let isPlaying;
let width;
let height;
let margin;
let x;
let y;
let dim;
let volumeSlider;
let timeSlider;
let parentTag;
let filename;
let playMeta;
p.windowResized = function () {
width = innerWidth * 0.6;
height = innerWidth * 9 / 16 * 0.18;
p.resizeCanvas(width, height);
margin = 20;
x = margin;
y = margin;
dim = height - 2 * margin;
volumeSlider = new slider(this, margin * 3 + dim, height * 0.8, width * 0.5 - margin * 4 - dim, volumeSlider.min, volumeSlider.max, volumeSlider.actualValue, volumeSlider.decimalPrecision, width, height);
timeSlider = new slider(this, margin * 3 + dim, height * 0.5, width - margin * 12 - dim, timeSlider.min, timeSlider.max, timeSlider.actualValue, timeSlider.decimalPrecision, width, height);
};
p.setup = function () {
p.soundFormats('mp3', 'ogg');
width = innerWidth * 0.6;
height = innerWidth * 9 / 16 * 0.18;
var window = p.createCanvas(width, height);
//window.parent(parentTag.id);
parentTag = p.canvas.parentElement;
filename = parentTag.getElementsByTagName('meta')[0].getAttribute('content');
track = new Audio(filename);
let element = p.createElement('h1', parentTag.id);
element.parent(parentTag.id);
track.addEventListener('ended', p.finished);
isPlaying = false;
margin = 20;
x = margin;
y = margin;
dim = height - 2 * margin;
track.volume = 0.1;
volumeSlider = new slider(this, margin * 3 + dim, height * 0.8, width * 0.5 - margin * 4 - dim, 0, 100, 20, 0, width, height);
timeSlider = new slider(this, margin * 3 + dim, height * 0.5, width - margin * 12 - dim, 0, track.duration, 0, 2, width, height);
track.addEventListener('loadeddata', function () {
timeSlider.max = track.duration;
timeSlider.Update();
});
playMeta = p.createElement('meta');
playMeta.attribute('name', 'isPlaying');
playMeta.attribute('content', 'false');
};
p.draw = function () {
p.clear();
p.background(0, 0, 0, 50);
p.noStroke();
p.fill(0, 20);
p.rectMode(p.CORNER);
p.rect(0, 0, dim + 2 * margin, dim + 2 * margin);
p.ellipseMode(p.CORNER);
let alpha = 100;
if (p.insideButton()) {
alpha = 200;
}
p.fill(255, 255, 255, alpha);
p.ellipse(x, y, dim, dim);
p.fill(0, 150);
p.noStroke();
if (isPlaying) {
let dist = dim * 0.15;
p.rectMode(p.CENTER);
let w = dim * 0.15;
let h = dim * 0.5;
p.rect(x + dim / 2 - dist, y + dim / 2, w, h);
p.rect(x + dim / 2 + dist, y + dim / 2, w, h);
} else {
p.beginShape();
let r = dim * 0.35;
let angle = 0;
let da = p.TWO_PI / 3;
for (let i = 0; i < 3; i++) {
angle = da * i;
p.vertex(Math.cos(angle) * r + x + dim / 2, Math.sin(angle) * r + y + dim / 2);
}
p.endShape(p.CLOSE);
}
timeSlider.Update();
timeSlider.showText(p.SecondsToTime(track.currentTime) + ' / ' + p.SecondsToTime(track.duration));
timeSlider.actualValue = track.currentTime;
timeSlider.onChange = function () {
track.currentTime = timeSlider.actualValue;
}
timeSlider.Render();
volumeSlider.Update();
volumeSlider.showText('Volume: ' + volumeSlider.value + '%');
track.volume = volumeSlider.value / 100;
volumeSlider.Render();
if (playMeta.elt.getAttribute('content') == 'false' && isPlaying) {
p.PauseTrack();
} else if (playMeta.elt.getAttribute('content') == 'true' && !isPlaying) {
p.PlayTrack();
}
};
p.PlayTrack = function () {
track.play();
isPlaying = true;
playMeta.attribute('content', 'true');
let audioPlayers = document.getElementsByClassName('audioPlayer');
let otherPlayers = [];
for (let i = 0; i < audioPlayers.length; i++) {
if (audioPlayers[i].id != parentTag.id) {
otherPlayers.push(audioPlayers[i]);
}
}
for (let i = 0; i < otherPlayers.length; i++) {
let metas = otherPlayers[i].getElementsByTagName('meta');
let others = [];
for (let j = 0; j < metas.length; j++) {
if (metas[j].getAttribute('content') != filename && metas[j].getAttribute('name') == 'isPlaying') {
others.push(metas[j]);
}
}
for (let j = 0; j < others.length; j++) {
let otherPlayMeta = others[j];
otherPlayMeta.setAttribute('content', false);
}
}
}
p.PauseTrack = function () {
track.pause();
isPlaying = false;
playMeta.attribute('content', 'false');
}
p.SecondsToTime = function (secs) {
let minutes = p.nf(p.floor(secs / 60), 2);
let seconds = p.nf(p.floor(secs % 60), 2, 0);
let time = minutes + ':' + seconds;
return time;
};
p.mousePressed = function () {
if (p.insideButton() && p.mouseButton == p.LEFT) {
if (isPlaying) {
p.PauseTrack();
} else {
p.PlayTrack();
}
}
if (volumeSlider.insideSlider() && p.mouseButton == p.LEFT) {
volumeSlider.followMouse();
} else if (timeSlider.insideSlider() && p.mouseButton == p.LEFT) {
timeSlider.followMouse();
}
};
p.mouseReleased = function () {
volumeSlider.letGo();
timeSlider.letGo();
};
p.finished = function () {
p.PauseTrack();
track.currentTime = 0;
timeSlider.actualValue = track.currentTime;
};
p.insideButton = function () {
return (p.dist(p.mouseX, p.mouseY, x + dim / 2, y + dim / 2) <= dim / 2);
};
sketchhandleFile = function (file) {
track = createAudio(file.data, '');
track.hide();
};
};
let audioPlayers = document.getElementsByClassName('audioPlayer');
for (let i = 0; i < audioPlayers.length; i++) {
let newP5 = new p5(audioPanel, audioPlayers[i].id);
}
class slider {
constructor(p, x, y, w, min, max, startVal, decPris) {
this.width = p.width;
this.height = p.height;
this.decimalPrecision = decPris;
this.x = x;
this.y = y;
this.w = w;
this.stickToMouse = false;
this.margin = this.width * 0.08;
this.offset = 0;
this.min = min;
this.max = max;
this.actualValue = startVal;
this.onChange = function () { };
this.p = p;
this.dotR = p.height * 0.05;
// this.dotX = map(startVal, min, max, this.x, this.x + w);
this.value = startVal;
this.Update();
}
Update() {
if (this.stickToMouse) {
this.actualValue = this.p.constrain(this.p.map(this.p.mouseX - this.offset, this.x, this.x + this.w, this.min, this.max), this.min, this.max);
this.onChange();
}
// console.log(this.actualValue);
// console.log(this.min);
// console.log(this.max);
// console.log(this.x);
this.dotX = this.p.map(this.actualValue, this.min, this.max, this.x, this.x + this.w);
this.value = Math.round(this.actualValue * 10 ^ this.decimalPrecision) / 10 ^ this.decimalPrecision;
}
Render() {
this.p.strokeWeight(this.height * 0.05);
this.p.stroke(255, 255, 255, 100);
this.p.strokeCap(this.p.SQUARE);
this.p.line(this.x, this.y, this.x + this.w, this.y);
this.p.noStroke();
let alpha = 150;
let magnifier = 0;
if (this.insideSlider() || this.stickToMouse) {
alpha = 255;
}
if (this.stickToMouse) {
magnifier = this.dotR;
}
this.p.fill(0, 0, 0, alpha);
this.p.rectMode(this.p.CENTER);
this.p.rect(this.dotX, this.y, this.dotR * 2 + magnifier, this.dotR * 2 + magnifier);
}
showText(txt) {
this.p.fill(0, 0, 0, 100);
//textFont(font);
this.p.textAlign(this.p.LEFT, this.p.CENTER);
this.p.textSize(20);
this.p.text(txt, this.x + this.w + this.margin / 3, this.y);
}
followMouse() {
this.stickToMouse = true;
this.offset = this.p.mouseX - this.dotX;
}
letGo() {
this.stickToMouse = false;
}
insideSlider() {
return (this.p.dist(this.p.mouseX, this.p.mouseY, this.dotX, this.y) <= this.dotR);
}
}
});
and here the part of the html file:
<div id="Interstellar - Piano Version" class="audioPlayer">
<meta name="file" content="audioTracks/interstellar.mp3">
</div>
The security policy of Firefox is different from chrome and edge. It may be because you open the html file directly on local machine. This will cause Chrome and Edge to be unable to directly access local resources. If you want to run in chrome and edge, please create a web server to run this file.
Be similar to this question.
And you can make chrome have permission to access the file. link
I am using a canvas with clickable elements that was added using for loop, I added a resizing event that redrawing the canvas after user window was resized, When the window is loading for the first time the canvas and click listeners works great, My problem starts after the window resizing, I getting wrong click coordinates and bad behavior, It looks the click event have sort of a backlog for all the times that the screen was resized
Here is the full code on stackblitz
The resize function
#HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
this.innerHeight = window.innerHeight;
this.canvas.width = this.innerWidth;
this.canvas.height = this.innerHeight
this.cleardraw()
this.draw()
}
cleardraw(){
var ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.innerWidth, this.innerHeight);
}
The draw function
draw() {
var ctx = this.canvas.getContext('2d');
ctx.font = "15px Arial";
var seats = []
var tempOrderArrey = []
var orderSeatsClinet = []
var selectedSeatsClient = []
var numberOfSeats = 10
var numberOfRows = 10
var canvasWidth = this.innerWidth
var canvasHight = this.innerHeight
function Seat(x, y, w, h, id, line, seatNum, color) {
this.x = x - 160
this.y = y ;
this.w = w;
this.h = h;
this.id = id;
this.line = line
this.seatNo = seatNum + 1 ;
this.color = color
}
Seat.prototype.draw = function () {
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.w, this.h)
}
function drawAll() {
for (var i = 0; i < seats.length; i++) {
seats[i].draw();
}
}
var id = 1;
var xPad = canvasWidth / 30
function addSeats(value, ch) {
for (let i = 0; i <= 15; i++)
seats.push(new Seat( (canvasWidth / 3) + (i * xPad), value, canvasWidth/ 37, canvasWidth/ 37, id++, ch, i, "#998515"));
}
var start = 60, diff = canvasWidth/30, ch = 0;
for (let i = 0; i < 2; i++) {
//60 + (40 * i)
addSeats(start + (diff * i), ch++);
}
drawAll()
The click event function
this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
let cX = event.layerX;
let cY = event.layerY;
const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
const offsetTop = this.canvasRef.nativeElement.offsetTop;
this.cX = cX - offsetLeft;
this.cY = cY - offsetTop;
for (var i = 0; i < seats.length; i++) {
var s = seats[i];
if (cX >= s.x && cX < s.x + s.w && cY >= s.y && cY < s.y + s.h) {
if (s.color == '#998515') { // If green
tempOrderArrey.push({ "id": s.id, "seatNum": s.seatNo, "rowNum": s.line })
s.color = '#ff0000'
ctx.fillStyle = '#ff0000'
ctx.fillRect(s.x, s.y, s.w, s.h)
}
else if (s.color == '#ff0000') { // If red
tempOrderArrey = tempOrderArrey.filter(seat => seat.id != s.id);
ctx.fillStyle = '#998515'
s.color = '#998515'
ctx.fillRect(s.x, s.y, s.w, s.h)
}
}
this.tempOrderArrey = tempOrderArrey
}})
}
The reason is that each time you resize, renderer.listen is called again, so for one click there are many events being fired.
You need to make sure that you clear the listener before creating a new one
// Check if the reference exists
if (this.reference != null) {
this.reference; // Clear the listener
}
// Store a reference to the listener
this.reference = this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
Here is a fork of the StackBlitz
The Problem
I have been creating a game, I have got to a stage where I want to see what it looks like with a mockup background I have created.
The Question
Where about in my code should I place this code as the place it currently is doesnt show the background.
I want this background on the canvas, the dimensions are correct.
The Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
background_img.src = "background.png";
background_img.onload = function(){
context.drawImage(background_img, 0, 0);
}
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 20) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 20) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#410b11";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
ctx.drawImage(background_img, 0, backgroundY);
}
return {
init: init
};
}
As already pointed out in a comment, here more precisely:
First of all, the background picture must be rendered first in every animation frame.
However, the picture didn't show up at all. This is due to the fact that variable was used (backgroundY), which is never declared somewhere.
This should actually printed to the console as an error "backgroundY" is not defined.
Whenever an the property src of an image object is set to a value, it takes some time until it's loaded. So in many cases, it's necessary to indicate the moment, when it's finished loading by the onload callback.
In this case, however, it's not necessary. The tick / animation loop function will just draw nothing (an empty image object) until it's loaded. After it's loaded it will continue to draw the loaded image every frame.
If the background is really important, meaning, the app should only start, when it's there, of course, one can only start the whole game / animation from within the img.onload handler.
You must draw:
the background first
the player later
level/score info last
Background < Player < UI < You Looking
The drawing order is from back to top (painters algorithm)
Also note that for performance reasons if you background never changes you could draw it in another 'static' canvas under the game canvas.
Otherwise the background will be drawn above/over the player and hide it.
If I have an array in JavaScript that starts with var stars = [] and I create a star (code below). I found this code online and am working my way through it so that I can see how it works and to modify it.
Is the this.stars = stars; just creating another internal property for this particular class?
var stars = [];
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity))
+ this.minVelocity);
}
this.stars = stars; // <-- creating internal property
Because I do not see it here in the definition of the class. So I am not certain if it is just created on the spot or if it could be declared in this definition.
Code Here:
function Starfield() {
this.fps = 30;
this.canvas = null;
this.width = 0;
this.width = 0;
this.minVelocity = 15;
this.maxVelocity = 30;
this.stars = 9000;
this.intervalId = 0;
}
// The main function - initialises the starfield.
Starfield.prototype.initialise = function(div) {
var self = this; //sets it self to current object
// Store the div
this.containerDiv = div;
self.width = window.innerWidth;
self.height = window.innerHeight;
window.onresize = function(event) {
self.width = window.innerWidth;
self.height = window.innerHeight;
self.canvas.width = self.width;
self.canvas.height = self.height;
self.draw();
}
// Create the canvas.
var canvas = document.createElement('canvas');
div.appendChild(canvas);
this.canvas = canvas;
this.canvas.width = this.width;
this.canvas.height = this.height;
};
Starfield.prototype.start = function() {
// Create the stars.
var stars = []; //creates an array that can be used for anything but in this case a star field
//this.stars is a property in the class that contains a number of the stars
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity)) + this.minVelocity);
}
this.stars = stars;
var self = this;
// Start the timer.
this.intervalId = setInterval(function() {
self.update();
self.draw();
}, 1000 / this.fps);
};
Starfield.prototype.stop = function() {
clearInterval(this.intervalId);
};
Starfield.prototype.update = function() {
var dt = 1 / this.fps;
for(var i=0; i < this.stars.length; i++) {
var star = this.stars[i];
star.y += dt * star.velocity;
// If the star has moved from the bottom of the screen, spawn it at the top.
if (star.y > this.height) {
this.stars[i] = new Star(Math.random() * this.width,
0,
Math.random() * 3 + 1,
(Math.random() * (this.maxVelocity + 60 - this.minVelocity)) + this.minVelocity);
}
}
};
Starfield.prototype.draw = function() {
var ctx = this.canvas.getContext("2d");
// Draw the background.
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, this.width, this.height);
// Draw stars.
ctx.fillStyle = '#ffffff';
for(var i=0; i<this.stars.length;i++) {
var star = this.stars[i];
ctx.fillRect(star.x, star.y, star.size, star.size);
}
};
//This is the class for stars -- there are 4 properties that are in this particular class
function Star(x, y, size, velocity) {
this.x = x;
this.y = y;
this.size = size;
this.velocity = velocity;
}
Yes, properties can be added at any time in JavaScript. Take a look at this example:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.getFullName = function() {
// Notice that I haven't defined this.lastName yet
return this.firstName + ' ' + this.lastName;
};
var bob = new Person('Bob');
bob.lastName = 'Saget';
console.log(bob.getFullName()); // 'Bob Saget'
Yes, Javascript objects are dynamic. They can have new properties added/deleted at any time unless they have been sealed and their properties can be modified at any time unless they have been frozen.
You probably won't see many sealed or frozen objects in the wild.