I'm making a simple program of an etch-a-sketch. I have 2 buttons. 1 that resets the screen, the other makes a new screen and lets you pick the number of pixels in the draw area. The default size works, and the reset works. When I click the new button and set the number of pixels in the draw area updates but the eventlistener stops working and the mouse over won't change the the background color of the divs anymore. Here is my code:
const screen = document.querySelector('.screen')
const clearButton = document.querySelector('.clear-btn');
const newButton = document.querySelector('.new-btn');
var size = 64;
function createGrid(size) {
document.styleSheets[0].cssRules[3].style["grid-template-columns"] = "repeat(" + size + ", 1fr)"
document.styleSheets[0].cssRules[3].style["grid-template-rows"] = "repeat(" + size + ", 1fr)"
console.log('createGrid');
for (i = 0; i < size*size; i++) {
const grid = document.createElement('div');
grid.classList.add('grid');
grid.style.cssText = 'color: #cccccc; background: #cccccc; border: solid 1px lightgrey;';
screen.appendChild(grid);
}
}
function reset() {
for (i = 0; i < size*size; i++) {
grid[i].style.backgroundColor = "#cccccc";
}
}
function newSize(){
let userResponse = prompt("Please enter size of canvas: ", "");
size = parseInt(userResponse);
remove();
createGrid(size);
}
function remove(){
while (screen.firstChild) {
console.log(size);
screen.removeChild(screen.firstChild);
}
}
createGrid(size);
clearButton.addEventListener('click', reset);
newButton.addEventListener('click', newSize);
var grid = document.getElementsByClassName('grid');
Array.from(grid).forEach((tile) => {
tile.addEventListener('mouseover', (e) => {
e.target.style.background = '#0d0d0d';
});
})
You need to add your event listener when you are creating the grid. You remove your grid, which the event original event listener has been attached and the newly created one doesn't have anything attached to it:
function createGrid(size) {
document.styleSheets[0].cssRules[3].style["grid-template-columns"] = "repeat(" + size + ", 1fr)"
document.styleSheets[0].cssRules[3].style["grid-template-rows"] = "repeat(" + size + ", 1fr)"
console.log('createGrid');
for (i = 0; i < size*size; i++) {
const grid = document.createElement('div');
grid.classList.add('grid');
grid.style.cssText = 'color: #cccccc; background: #cccccc; border: solid 1px lightgrey;';
screen.appendChild(grid);
// Add listener to grid cell
grid.addEventListener('mouseover', (e) => {
e.target.style.background = '#0d0d0d';
})
}
}
You might also have a look at event delegation so that you don't have to add a listener per cell and can add it on the container.
The reason for this is that you now have new divs. Your listener was attached to the original HTMLNodes, and now you added new ones, they do not have any listeners attached. The solution would be to:
- clean up the old listeners (in remove function)
- attach the new listeners (in newSize function)
Move the last part (from var grid = document.getElementsByClassName...) to a function, and call it at the end of new size function, something like this:
function atachListeners() {
const grid = document.getElementsByClassName('grid');
Array.from(grid).forEach(tile => {
tile.addEventListener('mouseover', ...);
});
};
Now, your newSize function is like this:
function newSize(){
let userResponse = prompt("Please enter size of canvas: ", "");
size = parseInt(userResponse);
remove();
createGrid(size);
attachListeners();
}
And the remove gets the addition:
function remove() {
removeTileListeners();
while (screen.firstChild) {
screen.removeChild(screen.firstChild);
}
}
Implementing the removeListeners() method, I leave to you as homework :)
Related
What I'm trying to achieve is to loop and toggle a class on every individual div every second.
Below is the code to create 4 divs with colour from an array.
const container = document.querySelector(".container");
const colors = ["#FDB10B", "#FE8535", "#FD292F", "#B20000"];
const ball = container.querySelectorAll("div");
for (let i = 0; i < colors.length; i++) {
const balls = document.createElement("div");
balls.style.backgroundColor = colors[i];
container.appendChild(balls);
}
Now I want to loop through the containers element every second and Google a class on the elements.
I tried using the setInterval() method
I've added a class "ball" you could as well refer to element by container > div, but easier to understand this way.
const container = document.querySelector(".container");
const colors = ["#FDB10B", "#FE8535", "#FD292F", "#B20000"];
const ball = container.querySelectorAll("div");
for (let i = 0; i < colors.length; i++) {
const balls = document.createElement("div");
balls.style.backgroundColor = colors[i];
balls.classList.add('ball');
container.appendChild(balls);
}
setInterval(function() {
toggle();
}, 1000)
function toggle() {
const tog = document.querySelector('.class-to-toggle');
if (tog) {
tog.classList.remove('class-to-toggle');
const nextTog = tog.nextElementSibling;
if (nextTog) {
nextTog.classList.add('class-to-toggle');
} else {
document.querySelector('.ball').classList.add('class-to-toggle');
}
} else {
document.querySelector('.ball').classList.add('class-to-toggle');
}
}
<div class="container">
</div>
if you inspect in dev tools you'll see class changed
Here is another example of the way you could do this.
Your question lacks some details about what you need to do so the example is quite generic.
const container = document.querySelector(".container");
const colors = ["#FDB10B", "#FE8535", "#FD292F", "#B20000"];
colors.forEach(color => {
const ball = document.createElement("div");
ball.classList.add("ball");
// Added some stylnig value for the example
ball.style.width = "20px";
ball.style.height = "20px";
ball.style.backgroundColor = color;
container.appendChild(ball);
});
const balls = container.querySelectorAll(".ball");
let cursor = 0;
const int_id = setInterval(selectNextBall, 1000);
function selectNextBall() {
const selectedBall = balls[cursor];
// I do this just to illustrate the example,
// could be anything you need to do with your ball.
for (const ball of balls) {
ball.style.border = ball === selectedBall ? "1px solid black" : "none";
}
// for an endless loop
cursor = cursor === balls.length - 1 ? 0 : cursor + 1;
// Or if the loop need to iterate just once
/*
if (cursor === balls.length - 1) {
clearInterval(int_id);
}
cursor++
*/
}
<div class="container">
</div>
What I want is when the player overlaps with the coin, the coin disappears, but its not for some reason and I don't why the cutcoin function is not called.
function create() {
var healthGroup = this.physics.add.staticGroup({
key: 'ycoin',
frameQuantity: 10,
immovable: true
});
var children = healthGroup.getChildren();
for (var i = 0; i < children.length; i++)
{
var x = Phaser.Math.Between(50, 750);
var y = Phaser.Math.Between(50, 550);
children[i].setPosition(x, y);
}
healthGroup.refresh();
moveCoin = this.add.sprite(60, 340, "ycoin").setInteractive();
this.input.keyboard.on('keydown-A', () => {
moveCoin.allname = "green"
diceNumber = 1
moveCoinStep()
setTimeout(() => {
this.physics.add.overlap(moveCoin,healthGroup,
cutcoin,null,this)
}, 1000);
})
}
function cutcoin(movecoin1, healthGroup) {
console.log('++++++', moveCoin, healthGroup)
}
Don't add this line
this.physics.add.overlap(moveCoin,healthGroup,cutcoin,null,this)
inside keyboard event or on timeout. overlap function is only triggered when overlap occur not on definition. so write the add overlap line outside the keyboard event.
And check the console for any error.
I'm using the 'timeupdate' event listener to sync a subtitle file with audio.
It is working currently, but I'd like to adjust it to where it is just highlighting the corresponding sentence in a large paragraph instead of deleting the entire span and replacing it with just the current sentence. This is the sort of functionality I am trying to replicate: https://j.hn/lab/html5karaoke/dream.html (see how it only highlights the section that it is currently on).
This is made complicated due to timeupdate constantly checking multiple times a second.
Here is the code:
var audioSync = function (options) {
var audioPlayer = document.getElementById(options.audioPlayer);
var subtitles = document.getElementById(options.subtitlesContainer);
var syncData = [];
var init = (function () {
return fetch(new Request(options.subtitlesFile))
.then((response) => response.text())
.then(createSubtitle);
})();
function createSubtitle(text) {
var rawSubTitle = text;
convertVttToJson(text).then((result) => {
var x = 0;
for (var i = 0; i < result.length; i++) {
if (result[i].part && result[i].part.trim() != '') {
syncData[x] = result[i];
x++;
}
}
});
}
audioPlayer.addEventListener('timeupdate', function (e) {
syncData.forEach(function (element, index, array) {
if (
audioPlayer.currentTime * 1000 >= element.start &&
audioPlayer.currentTime * 1000 <= element.end
) {
while (subtitles.hasChildNodes()) {
subtitles.removeChild(subtitles.firstChild);
}
var el = document.createElement('span');
el.setAttribute('id', 'c_' + index);
el.innerText = syncData[index].part + '\n';
el.style.background = 'yellow';
subtitles.appendChild(el);
}
});
});
};
new audioSync({
audioPlayer: 'audiofile', // the id of the audio tag
subtitlesContainer: 'subtitles', // the id where subtitles should show
subtitlesFile: './sample.vtt', // the path to the vtt file
});
I am making a page that uses buttons to display the content when the creen is wider than 500px, and then it moves to an 'accordion' style layout when the screen is smaller than 500px.
I am using event listeners and match media along with an if/else statement to determine which code to run.
However, when the code is run with a window of size less than 500px, then dragged out to make it bigger than 500px, the buttons are not responding to the 'onclick' when pressed and nothing happens.
Could somebody please advise where I am going wrong here?
Please see JSfiddle here
//Start listing variables for use in array.
var art1 = document.getElementById("article1");
var button1 = document.getElementById("list1");
var art2 = document.getElementById("article2");
var button2 = document.getElementById("list2");
var art3 = document.getElementById("article3");
var button3 = document.getElementById("list3");
var articleArray = [art1, art2, art3];
var buttonArray = [button1, button2, button3];
function mediaQuery(x) {
//If window is under 500px in width.
if (x.matches) {
//Begin accordion code
var initAccordion = function(accordionElement) {
function handlePanelClick(event) {
showPanel(event.currentTarget);
}
function showPanel(panel) {
var expandedPanel = accordionElement.querySelector(".active");
if (expandedPanel) {
expandedPanel.classList.remove("active");
}
panel.classList.add("active");
}
var allPanelElements = accordionElement.querySelectorAll(".panel");
for (var y = 0; len = allPanelElements.length; y++ ) {
allPanelElements[y].addEventListener("click", handlePanelClick);
}
showPanel(allPanelElements[0]);
}
initAccordion(document.getElementById("contentWrapper"));
}
else //If window if is over 500px in width.
{
//Begin button code.
var createfunc = function (i) {
return function() { document.getElementById("fillMe").innerHTML = articleArray[i].innerHTML;};
}
for (let i=0; i < articleArray.length; i++) {
let button = buttonArray[i];
button.addEventListener("click", createfunc(i));
}
}
}
//Declare media query and add listener.
var x = window.matchMedia("(max-width: 500px)")
mediaQuery(x) // Call listener function at run time
x.addListener(mediaQuery) // Attach listener function on state changes
Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.