Refer to link. Link is to the project "Scroll-Animation" I am cloning in local browser to practice front-end.
I have two buttons, +, -. The '+' adds new boxes, the '-' removes boxes from the body. I implemented the '+' but it seems the styling for boxes is not working. That is, it seems to add the "div" elements I created to body but the div's are not style like I have them in .css file for .box { ... } Any help with how to fix this is greatly appreciated. Thanks!
style.css
...
.box {
height: 200px;
width: 400px;
border-radius: 10px;
background-color: steelblue;
margin: 10px;
color: white;
display: grid;
place-items: center;
box-shadow: 2px 4px 5px rgba(0,0,0,.3);
transform: translateX(-400%);
transition: transform .5s ease;
}
.box:nth-of-type(even) {
transform: translateX(400%);
}
.box.show {
transform: translateX(0);
}
...
app.js
const boxes = document.querySelectorAll('.box');
const btns = document.querySelectorAll('.btn');
window.addEventListener('scroll', slideBox);
slideBox();
// NOTE: Need to fix.
btns.forEach(btn => {
btn.addEventListener('click', (e) => {
const cList = e.currentTarget.classList;
if (cList.contains('add')) {
console.log('Work');
var h2 = document.createElement('h2');
h2.innerHTML = 'Content';
var newBox = document.createElement("div");
var attr = document.createAttribute("class");
attr.value = "box";
newBox.setAttributeNode(attr);
newBox.appendChild(h2);
document.body.appendChild(newBox);
}
})
})
// NOTE: This function works!!!
function slideBox() {
const pageBot = window.innerHeight / 5 * 4;
const pageTop = window.innerHeight / 5 * 1;
boxes.forEach(box => {
const boxTop = box.getBoundingClientRect().top;
const boxBot = box.getBoundingClientRect().bottom;
if (boxTop < pageBot && boxBot > pageTop) {
box.classList.add('show');
} else {
box.classList.remove('show');
}
})
}
index.html
<body>
<h1>Scroll to see the animation</h1>
<div class="button-container">
<button class="btn add">+</button>
<button class="btn minus">-</button>
</div>
<!-- <div class="box"><h2>Content</h2></div>
<div class="box"><h2>Content</h2></div>
...
<div class="box"><h2>Content</h2></div> -->
<script src="app.js"></script>
</body>
I think the problem is after you add a new box, but afterward you don't query all boxes again in slideBox. You can check the fix here
The full modification in Javascript with some comments
const btns = document.querySelectorAll('.btn');
window.addEventListener('scroll', slideBox)
slideBox();
btns.forEach(btn => {
btn.addEventListener('click', (e) => {
const cList = e.currentTarget.classList;
if (cList.contains('add')) {
console.log('Work');
var h2 = document.createElement('h2');
h2.innerHTML = 'Content';
var newBox = document.createElement("div");
var attr = document.createAttribute("class");
attr.value = "box";
newBox.setAttributeNode(attr);
newBox.appendChild(h2);
document.body.appendChild(newBox);
}
})
})
function slideBox() {
//query all boxes every time you scroll because of new boxes
const boxes = document.querySelectorAll('.box')
const triggerBottom = window.innerHeight / 5 * 4
boxes.forEach(box => {
const boxTop = box.getBoundingClientRect().top
if (boxTop < triggerBottom) {
box.classList.add('show')
} else {
box.classList.remove('show')
}
})
}
Related
Fairly new to JavaScript so bear with me!
I'm currently building a simple TV show rating search app, using TVmaze's API and I'm having trouble figuring out how I can have the search bar move to the top of the page after the search button is clicked. I tried creating a function called searchBarPos to remove the class "center" but I can't seem to figure out why it won't work within my click event listener. Any tips on how to solve this is much appreciated!
JS code:
const form = document.querySelector("#searchForm");
let div = document.querySelector("#container");
const ul = document.querySelector("#ratingList");
const reset = () => {
ul.innerText = '';
}
form.addEventListener('submit', async function (e) {
e.preventDefault();
reset();
const showName = form.elements.query.value;
const config = { params: { q: showName } }
const res = await axios.get(`https://api.tvmaze.com/search/shows?q=`, config)
display(res.data);
form.elements.query.value = '';
});
const display = (shows) => {
for (let result of shows) {
if (result.show.image) {
const img = document.createElement('IMG');
img.classList.add('movieData')
img.src = result.show.image.medium;
ul.append(img);
}
if (result.show.name && result.show.premiered) {
const title = result.show.name;
const date = result.show.premiered;
const li = document.createElement('li');
li.classList.add('movieData')
li.textContent = `${title} (${date})`;
ul.append(li);
}
if (result.show.rating) {
const avg = result.show.rating.average;
const li = document.createElement('li');
li.classList.add('movieData')
li.textContent = `Rating: ${avg}`;
ul.append(li);
}
}
}
CSS code:
body {
text-align: center;
}
.center {
margin:0;
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#title {
margin-top: 2rem;
font-family: 'M PLUS Code Latin', sans-serif;
}
form {
margin-bottom: 3rem;
}
.movieData{
margin: 0.5rem;
font-family: 'M PLUS Code Latin', sans-serif;
font-size: 18px;
}
i guess you can do it by adding inline CSS with javascript because of priority of inline CSS . try using:
document.querySelector().addEventHandeler('click', ()=>{
document.querySelector().style.position= "absolute";
document.querySelector().style.top= "0";
....
})
and taking it back with another eventhandeler when somewhere else in the page is clicked.
could someone help me out with this piece of Javascript?
I am trying to make some sort of "whack-a-mole" game, and this is what I came up with; I set up a way to keep track of the score by adding 1 (score++) every time the user clicks on the picture that pops up. My problem is that the code runs the function more times than needed—for example, if I click on the first image that pops up, the function to add +1 to the score fires once, if I click on the second, the function fires twice, threee times on the third, etc...
What am I doing wrong?
//gid
const grid = document.querySelector('.grid');
//score display value
const scoreValue = document.querySelector('#scoreValue');
//score
let score = 0;
const timer = setInterval(() => {
//output random number
let output = Math.floor(Math.random() * 16);
//select hole
let hole = document.getElementById(output);
hole.innerHTML = '<img src="img/kiseki.png" alt=""></img>';
setTimeout(() => {
hole.innerHTML = '';
}, 2000);
grid.addEventListener('click', e => {
if (e.target.tagName === "IMG") {
score++;
scoreValue.textContent = score;
console.log(score);
hole.innerHTML = '';
}
});
}, 4000);
Since you're ading a new eventListener every time the interval runs, so in order to solve your problem, just add it once, before starting the setInterval that pops your moles.
Example code:
const grid = document.querySelector('.grid');
const scoreValue = document.querySelector('#scoreValue');
const newMoleTimer = 4000;
const moleTimeout = 2000
let score = 0;
let hole;
grid.addEventListener('click', e => {
if (e.target.tagName === "IMG") {
score++;
scoreValue.textContent = score;
if(hole) hole.innerHTML = '';
}
});
const timer = setInterval(() => {
let output = Math.floor(Math.random() * 16);
hole = document.getElementById(output);
hole.innerHTML = '<img src="img/kiseki.png" alt=""></img>';
setTimeout(() => {
hole.innerHTML = '';
}, moleTimeout);
}, newMoleTimer);
*updated code according to #Meika commentary
You need to separate the eventlistener from the settimer function.
In this example I created div elements with a color. Only blue color score and can only score one point pr. timer.
//gid
const grid = document.querySelector('#grid');
//score display value
const scoreValue = document.querySelector('#scoreValue');
//score
let score = 0;
grid.addEventListener('click', e => {
if (e.target.score) {
score++;
scoreValue.textContent = score;
e.target.score = false;
}
});
const timer = setInterval(() => {
//output random number
let output = 1 + Math.floor(Math.random() * 3);
//select hole
let hole = document.querySelector(`div.box:nth-child(${output})`)
hole.classList.add('blue');
hole.score = true;
setTimeout(() => {
hole.classList.remove('blue');
hole.score = false;
}, 1000);
}, 2000);
div#grid {
display: flex;
}
div.box {
width: 100px;
height: 100px;
border: thin solid black;
background-color: red;
}
div.blue {
background-color: blue;
}
<div id="grid">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
<div id="scoreValue"></div>
Rewrite, a mole is a DOM element, attach the click event to it on load then, in the game timer you only need to pick a random mole and toggle a class, within the click event you can check for that class, if it is there then the mole must be showing, add a score.
For example:
const moles = document.querySelectorAll('.grid .mole')
const hitScore = document.querySelector('.score .hit')
const missScore = document.querySelector('.score .miss')
const gameOver = document.querySelector('.gameover')
let score = {
hit: 0,
miss: 0
}
// assign clicks to all moles
moles.forEach((elm) => {
elm.addEventListener('click', e => {
if (e.target.classList.contains('show')) {
hitScore.textContent = ++score.hit
e.target.classList.remove('show')
}
})
})
// game timer
const timer = setInterval(() => {
// get random mole element
const randMole = moles[Math.floor(Math.random() * moles.length)]
// check if has class, i.e miss
if (randMole.classList.contains('show')) {
missScore.textContent = ++score.miss
}
// toggle show
randMole.classList.toggle('show')
// 5 misses and game over
if (score.miss >= 5) {
clearInterval(timer)
gameOver.style.display = 'block'
}
}, 1000)
.grid {
width: 310px;
height: 310px;
background-image: url(https://i.imgur.com/s6lUgud.png);
position: relative
}
.mole {
position: absolute;
width: 100px;
height: 100px
}
.mole.show {
background-image: url(https://i.imgur.com/uScpWV4.png);
background-repeat: no-repeat;
background-size: 48px 51px;
background-position: center
}
.mole:nth-of-type(1) {
top: 0;
left: 0
}
.mole:nth-of-type(2) {
top: 0;
left: 108px
}
.mole:nth-of-type(3) {
top: 0;
left: 214px
}
.mole:nth-of-type(4) {
top: 100px;
left: 0
}
.mole:nth-of-type(5) {
top: 100px;
left: 108px
}
.mole:nth-of-type(6) {
top: 100px;
left: 214px
}
.mole:nth-of-type(7) {
top: 200px;
left: 0px
}
.mole:nth-of-type(8) {
top: 200px;
left: 107px
}
.mole:nth-of-type(9) {
top: 200px;
left: 214px
}
.gameover {
display: none;
color: red
}
<div class="score">
<strong>Score:</strong> Hit:
<span class="hit">0</span> Miss:
<span class="miss">0</span>
</div>
<div class="gameover">Game Over</div>
<div class="grid">
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
<div class="mole"></div>
</div>
I am trying to append my variables 'showName' and 'showDescription' to the 'results' div object. I have tried to add them in using 'innerHTML' but I just get the description shown. I have tried making additional divs to put INSIDE the 'results' div but that didn't work either.
I want the 'showName' to appear above the 'showDescription in the div.
I am challenging myself to not use JQuery so that is not a viable option.
code:
document.querySelector('.search').addEventListener('keypress', function(e){//On button click of enter, get the value of the search bar and concatanate it to the end of the url
if(e.key==='Enter'){
var query = document.getElementById('main').value;
var url = fetch("http://api.tvmaze.com/search/shows?q="+query) //use fetch to get the data from the url, THEN convert it to json THEN console.log the data.
.then(response => response.json())
.then(data => {
console.log(data)
var domObject = document.createElement('div')
domObject.id="myDiv";
domObject.style.width="800px";
domObject.style.height="5000px";
domObject.style.display="flex";
domObject.style.flexDirection="column";
domObject.style.margin="auto";
domObject.style.borderRadius="30px";
domObject.style.background="";
document.body.appendChild(domObject);
for (var i = 0; i < data.length; i++) { //for all the items returned, loop through each one and show the name of the show and the dsescription of the show.
var showName = data[i].show.name;
//console.log(showName);
var showDescription = data[i].show.summary
//console.log(showDescription);
var results = document.createElement('div')
results.id="myResults";
results.style.width="600px"
results.style.height="400px";
results.style.background="white";
results.style.margin="auto";
results.style.borderRadius="30px";
results.style.fontFamily="Poppins"
results.style.display="flex";
results.style.flexDirection="column";
results.innerHTML=showName;
results.innerHTML=showDescription;
document.getElementById("myDiv").appendChild(results);
}
})
}
});
document.querySelector('.search').addEventListener('keydown', function(o){
if(o.key==='Backspace'){
location.reload();
}
});
result of searching in 'car'
results.innerHTML = showName;
results.innerHTML = showDescription;
With this you are overwriting showName with showDescription.
What you need to do is concatenate with +=.
Also, it will be much easier to replace this:
domObject.style.width = "800px";
domObject.style.height = "5000px";
domObject.style.display = "flex";
domObject.style.flexDirection = "column";
domObject.style.margin = "auto";
domObject.style.borderRadius = "30px";
domObject.style.background = "";
with domObject.classList.add('some-class');
and CSS will be:
.some-class {
width: 800px;
height: 500px;
// etc...
}
Moved your code to a working example.
Note: because of authors styles, it is only possible to run snippet in fullscreen. =)
const dosearch = () => {
var query = document.getElementById('main').value;
var url = fetch("https://api.tvmaze.com/search/shows?q=" + query)
.then(response => response.json())
.then(data => {
const myDiv = document.getElementById("myDiv");
myDiv.innerHTML = ''; // <---- this is for testing
for (var i = 0; i < data.length; i++) {
var showName = data[i].show.name;
var showDescription = data[i].show.summary
var results = document.createElement('div');
results.className = 'myResults';
var header = document.createElement('h2');
header.innerHTML = showName;
results.appendChild(header);
var desc = document.createElement('div');
desc.innerHTML = showDescription;
results.appendChild(desc);
myDiv.appendChild(results);
}
});
}
document.querySelector('.search').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
dosearch();
}
});
#myDiv {
width: 800px;
height: 5000px;
display: flex;
flex-direction: column;
margin: auto;
border-radius: 30px;
background: black;
}
.myResults {
width: 600px;
height: 400px;
background: white;
margin: auto;
border-radius: 30px;
font-family: Poppins;
display: flex;
flex-direction: column;
}
.myResults p, .myResults h2 {
margin: 1em;
}
<input type="text" id="main" class="search" style="margin-bottom: 4px" value="Car" /><button onclick="dosearch()">Go</button>
<div id="myDiv"></div>
I am making a board game ludo. I contains four players. The board of ludo looks something like
I have managed to create some part of it dynamically using Javascript.
const qs = str => document.querySelector(str);
const qsa = str => document.querySelectorAll(str);
const ce = (str, props) => {
let elm = document.createElement(str);
if(props){
for(let k in props){
elm[k] = props[k]
}
}
return elm;
}
let main = qs('#main');
function createDiv(type, color){
let div = ce('div');
div.style.backgroundColor = color;
div.style.display = type;
}
let game = Array(52).fill(0);
function createPlayer(color,angle){
let div = ce('div', {className: 'player-cont'})
let table = ce('table');
div.style.transform = `rotate(${angle}deg)`
function createRow(len,colorsSet){
const tr = ce('tr',{className:'tile-row'});
[...Array(len)].forEach((x,i) =>{
let elm = ce('td', {className:'tile'});
if(colorsSet.has(i)){
elm.style.backgroundColor = color;
}
tr.appendChild(elm)
})
return tr;
}
function createBase(){
const base = ce('table', {className: 'base'});
[...Array(2)].forEach(x => {
let row = ce('tr', {className: 'base-row'});
[...Array(2)].forEach(a => {
let td = ce('td', {className: 'base-tile'})
row.appendChild(td);
})
base.appendChild(row)
})
return base;
}
let colorSets = [
new Set(),
new Set([0,1,2,3,4]),
new Set([4])
]
colorSets.forEach(x => {
table.appendChild(createRow(6, x))
})
div.appendChild(table);
div.appendChild(createBase());
return div;
}
let colors = ['red','blue','green','pink'];
colors.forEach((x, i) => {
main.appendChild(createPlayer(x, (i * 90) - 180));
})
.tile {
height: 50px;
width: 50px;
background: orange;
border: 1px solid black;
display: inline-block;
}
table.base td {
height: 50px;
background: orange;
width: 50px;
border-radius: 100px;
}
.player-cont {
background: aliceblue;
width: fit-content;
}
<div id = "main"></div>
But now I am not sure how will I finish this up. You can ignore two things in the above image:
The big border of the of big square of different colors. I just need a single color throughout.
You can ignore the colorful triangles in center. I just need a one color box instead of that.
I am trying to make an interface where you can select tickets and buy them, I want that when I click on a seat it displays like "You are currently buying the next tickets + (The tickets chosen by the user)".
This is my code so far:
var seatsUnclicked = document.getElementsByClassName("seat-unique");
var seatsClicked = document.getElementsByClassName("seatClicked");
var images = document.getElementsByTagName("IMG");
var seatsOutput = document.getElementsById("seatsOutput");
var ticketsData = 0
for (let i = 0; i < seatsUnclicked.length; i++) {
seatsUnclicked[i].onmouseover = function() {
this.src = "chairclicked.svg";
this.onmouseout = function() {
this.src = "chair.svg"
}
if ($(this).hasClass('seatClicked')) {
this.src = "chairclicked.svg";
this.onmouseout = function() {
this.src = "chairclicked.svg"
}
}
}
seatsUnclicked[i].onclick = function() {
this.classList.add("new")
if ($(this).hasClass('seatClicked')) {
this.classList.remove("seatClicked")
this.classList.remove("new")
this.src = "chair.svg";
this.onmouseout = function() {
this.src = "chair.svg"
}
ticketsData = ticketsData - /* "the id of this element in a string" */
}
if ($(this).hasClass('new')) {
this.src = "chairclicked.svg";
this.classList.add("seatClicked")
this.classList.remove("new")
this.onmouseout = function() {
this.src = "chairclicked.svg"
}
ticketsData = ticketsData + /* "the ID of this element in a string" */
}
seatsOutput.innerHTML = "THE TICKETS YOU HAVE CHOSEN ARE" + string(ticketsData)
}
}
<div class="seats-row-A">
<img id="A1" class="seat-unique " src="http://via.placeholder.com/100x100?text=A1">
<img id="A2" class="seat-unique " src="http://via.placeholder.com/100x100?text=A2">
<img id="A3" class="seat-unique " src="http://via.placeholder.com/100x100?text=A3">
<img id="A4" class="seat-unique " src="http://via.placeholder.com/100x100?text=A4">
<img id="A5" class="seat-unique" src="http://via.placeholder.com/100x100?text=A5">
<img id="A6" class="seat-unique " src="http://via.placeholder.com/100x100?text=A6">
<img id="A7" class="seat-unique " src="http://via.placeholder.com/100x100?text=A7">
</div>
<h2 id="seatsOutput">Chosen Tickets:</h2>
jQuery
The only jQuery statement in OP code is: $(this).hasClass('seatClicked').
The plain JavaScript equivalent is: this.classList.contains('seatClicked').
Question
I couldn't follow the OP code because there was only a class, an id, and img tags that match the JavaScript, but it's not that clear because of the *.svg files (not provided.) Also, there's a curly bracket } missing (I think it belongs to the for loop, but I'm not wasting time on debugging typos.)
The Demo was built in mind with what the question and comments had mentioned:
"...I want that when I click on a seat it displays like "You are currently buying..."
Highlight icon when hovered over.
Reveal icon's id when hovered on.
All hover behavior is done with CSS: :hover, ::before, ::after, content: attr(id), content: '\a0\1f4ba'. Using JavaScript for behavior CSS can do will result in more CPU cycles. CSS will use GPU of your graphics card instead of the CPU.
Testing
The seats are dynamically generated with id="A* by entering a number in the input and clicking the View button. For each additional click of the button a new batch of seats are appended and have ids that correspond to it's group:
input: 55 and first click A0-A55,
input: 12 and second click B0-B12,
input: 222 and third click C0-C222
...
Last group is J
References
The Demo is basically a <form>. HTMLFormControlsCollection API is used to set/get form controls and values.
Reference the tag
const ui = document.forms.tickets;
This is a collection of all form controls in form#tickets
const t = ui.elements;
Now all form controls are now accessible by prefixing a form control's #id or [name] with the HTMLFormControlsCollection Object.
<textarea name='textA'></textarea>
Without HFCC API
var ta = document.querySelector('[name=textA]');
With HFCC API
var ta = t.textA;
The links are collected by Links Collection.
document.links
DocumentFragment is used to insert a huge amount of dynamic HTML in one shot efficiently and quickly.
document.createDocumentFragment();
Various array methods were used:
Array.from()
map()
fill()
indexOf()
Demo
const ui = document.forms.tickets;
const t = ui.elements;
const seats = document.getElementById('seats');
t.btn.addEventListener('click', seatsAvailable);
seats.addEventListener('click', function(e) {
let picked = [];
pickSeat(e, picked);
}, false);
function pickSeat(e, picked) {
const display = t.display;
if (e.target.tagName === "A") {
e.target.classList.toggle('picked');
picked = Array.from(document.querySelectorAll('.picked'));
}
picked = picked.map(function(seat, index, picked) {
return seat.id;
});
display.value = "";
display.value = picked;
}
function seatsAvailable(e) {
const qty = this.previousElementSibling;
const last = document.links[document.links.length - 1].id;
console.log(last);
const limit = parseInt(qty.value, 10) + 1;
const spots = new Array(limit);
spots.fill(0, 0, limit);
return generateSeats(spots, last);
}
function generateSeats(spots, last) {
if (last.charAt(0) === "J") {
t.display.textContent += "Last row available";
return false;
}
const rowID = ['x', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
let row = rowID.indexOf(last.charAt(0)) + 1;
const frag = document.createDocumentFragment();
const avSeats = spots.map(function(A, idx) {
const seat = document.createElement('a');
seat.id = rowID[row] + idx;
seat.href = "#/";
frag.appendChild(seat);
return seat;
});
seats.appendChild(frag);
if (document.links[0].id === 'x') {
const x = document.getElementById('x');
x.parentElement.removeChild(x);
}
if (document.links.length > 114) {
const ext = (Math.round(document.links.length / 114)*600)+600;
seats.style.maxHeight = ext+'px';
}
return avSeats;
}
html,
body {
width: 100%;
height: 10%;
font: 400 16px/1.3 Consolas;
}
#seats {
display: flex;
flex-flow: column wrap;
max-height: 600px;
width: auto;
border: 3px ridge grey;
}
.box {
display: table
}
input,
button,
label {
font: inherit
}
#qty {
text-align: right
}
#display {
display: table-cell;
}
.tag {
overflow-x: scroll;
overflow-y: hidden;
display: block;
width: 400px;
line-height: 1.3
}
a,
a:link,
a:visited {
display: inline-block;
margin: 0;
text-align: center;
text-decoration: none;
transition: all 500ms ease;
}
a:hover,
a:active {
background: rgba(0, 0, 0, 0);
color: #2468ac;
box-shadow: 0 0 0 3px #2468ac;
}
a::before {
content: attr(id);
color: transparent;
}
a:hover::before {
color: #2468ac;
}
a.picked::before {
color: #000;
}
a::after {
content: '\a0\1f4ba';
font-size: 1.5rem;
}
#x {
pointer-events: none
}
.as-console-wrapper {
width: 30%;
margin-left: 70%
}
<form id='tickets'>
<fieldset class='box'>
<legend>Available Seats</legend>
<fieldset class='box'>
<input id='qty' type='number' min='0' max='50' value='1'> <button id='btn' type='button'>View</button>
<label class='tag'>Current ticket purchases to seats:
<output id='display'></output>
</label>
</fieldset>
<section id='seats'>
<a id='x' href='#/'></a>
</section>
</fieldset>
</form>