mouseover event bubbling issue - javascript

I am writing some pure Javascript that requires me to add elements to a flex container dynamically row by row. To my surprise, My mouseover event propagate across the row and trigger the other children even though it shouldn't. Below is my code:
function drawChildren() {
var size = Math.floor(containerSize / childSize);
var counter = 1;
var parent = document.getElementById(parentId);
for(var rowCount = 1; rowCount <= size ; rowCount ++) {
var row = document.createElement('div');
row.id = `${parentId}-rowDiv-${rowCount} `;
row.setAttribute('style', `
height: ${childSize}px;
width: ${containerSize}px;
display: flex;
flex-direction:row; `);
for(var child = 1; child <= size ; child ++) {
var childDiv = document.createElement('div');
childDiv.id = `${parentId}-childDiv-${counter}`;
childDiv.setAttribute('style',`
height: ${childSize}px;
width: ${childSize}px;
background-color: ${getRandomColor()};`);
childDiv.addEventListener("mouseover", onMouseOver);
childDiv.addEventListener("mouseleave", onMouseLeave);
row.appendChild(childDiv);
counter ++;
}
parent.appendChild(row);
}
onmouseover , I called the function below:
function onMouseOver(e) {
e.stopPropagation();
document.getElementById(e.target.id).style.display = 'none';
console.log(e.target.id);
}
The problem is, whenever I mouseover on an object, it propagates across the row and fires the mouseover event for all the other items on the same row. It does fire one at a time also. I tried to stop propagation by adding the js stopPropagation() prop yet nothing change. Please what is causing this and how do I address it? Any help would be appreciated.

The JS logic works just fine after removing the syntax used for getting the variables for size and parentId (which I'm guessing is from JSP). May be the backtick (`) used is the issue.
OR
You are referring to the problem where hovering on first child of the row hides the entire row.
Here, display:none; will be the culprit and you can use visibility: hidden; instead.
display: none; will remove the element from the layout, freeing its space taken from the layout and thus, allowing the next element to take up its space.
In the question, hovering on 1st child frees the space which is now taken by the 2nd element. Since your mouse is still at the same position, it will now remove the 2nd element and the cycle goes so on.
visibility: hidden; only hides the element while retaining its space in the layout of the page.
Here's a working snippet of your code (with display: none; and visibility : hidden;):
var containerSize = 200,
childSize = 50;
function onMouseOverDisplay(e) {
e.stopPropagation();
document.getElementById(e.target.id).style.display = 'none';
console.log(e.target.id);
}
function onMouseOverVisibility(e) {
e.stopPropagation();
document.getElementById(e.target.id).style.visibility = 'hidden';
console.log(e.target.id);
}
function setAttr(elem, attrs) {
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
elem.setAttribute(attr, attrs[attr]);
}
}
}
function drawChildren(parentId) {
var size = Math.floor(containerSize / childSize),
parent = document.getElementById(parentId),
counter = 1,
rowCount, childCount, row, childDiv;
for (rowCount = 1; rowCount <= size; rowCount++) {
row = document.createElement('div');
row.id = parentId + "-rowDiv-" + rowCount;
row.setAttribute('style', "height: " + childSize + "px; width: " + containerSize + "px; display: flex; flex-direction: row;");
for (childCount = 1; childCount <= size; childCount++) {
childDiv = document.createElement('div');
childDiv.id = parentId + "-childDiv-" + rowCount + "-" + childCount;
childDiv.setAttribute('style', "height: " + childSize + "px; width: " + childSize + "px; background-color: cyan; border: 1px solid red;");
if (parentId === 'tab-display') {
childDiv.addEventListener("mouseover", onMouseOverDisplay);
} else if (parentId === 'tab-visibility') {
childDiv.addEventListener("mouseover", onMouseOverVisibility);
}
// childDiv.addEventListener("mouseleave", onMouseLeave);
row.appendChild(childDiv);
counter++;
}
parent.appendChild(row);
}
}
drawChildren('tab-display');
drawChildren('tab-visibility');
<h2>Using Display None</h2>
<div id="tab-display"></div>
<h2>Using Visibilty Hidden</h2>
<div id="tab-visibility"></div>

Related

addEventListener is not working on js generated elements (images)

I'm placing a bunch of images on a grid in the center of the page and want to add a check for when each individual image is clicked. The images are created with js and added to the document, could it be an issue of them not being 'ready' yet or something?
function placePieces() {
for (var i = 0; i < setup.length; i++) {
if ((setup[i]+'' == "undefined")) {continue;}
var element = document.createElement("img");
element.src = "Images/" + pieces[Object.keys(pieces)[setup[i]]] + ".png";
element.style.width = "10vh";
element.style.height = "10vh";
element.style.marginTop = (Math.floor(i/8) * 10) + "vh";
element.style.marginLeft = "calc(((100vw - 80vh)/2) + " + (10 * (i%8) - 1) + "vh)";
element.style.zIndex = 10;
element.style.position = "absolute";
element.id = i+1;
document.body.innerHTML = "\n" + element.outerHTML + document.body.innerHTML;
console.log(element.outerHTML)
var nelement = document.getElementById(i+1);
console.log(nelement)
nelement.addEventListener("click",highlight);
}
}
placePieces()
function highlight(n) {
console.log(n)
n = n.currentTarget.myParam;
if (setup[n] == 0 || setup[n] == 6) {
var moves = [];
var m = n
while (True) {
if (!(Math.floor((m-9)/8)<=0)) {
console.log("test")
}
}
}
}
The second function is far from finished but it still does not return anything when it should.
You don't have to grab the element again, you can add the listener directly
const element = document.createElement('div')
element.style.background = 'red'
element.style.width = '100px'
element.style.height = '100px'
element.addEventListener('click', () => console.log('click'))
document.body.appendChild(element)
This should work:
function placePieces() {
for (var i = 0; i < setup.length; i++) {
if ((setup[i]+'' == "undefined")) {continue;}
var element = document.createElement("img");
// ...
element.addEventListener("click",highlight);
}
}
Event Handling
You have two choices:
Method
Pros
Cons
A
Bind (or register) the "click" event to each <img>.
Easier to write
Any dynamically added <img> must be registered to "click" event.
B
Bind the "click" event to an ancestor tag in which all <img>'s reside within. Write the event handler so that it only reacts when an <img> is clicked. This paradigm is called event delegation
Only needs to register to the event to only one tag once and any dynamically added <img> do not need any binding
Writing the event handler is harder.
When reviewing the example:
Click some <img> in Area A and B. There should be a blue outline.
Next, click both ADD buttons.
Click some of the new <img>s. The new <img> in Area A do not work.
Click the BIND button.
Click any of the new <img> in Area A
Details are commented in example
// File names of all images
const images = ["Fqsw6v8/2s", "Qb6N0dG/3s", "qnGtC68/4s", "nDFmjJB/5s", "sPtNDGm/6s", "HpmggvF/7s", "dKfcwxQ/8s", "K7HbrWp/9s", "9ys8PXt/as", "HVK2zvw/bs", "7SgXHz2/cs", "StdB11X/ds", "cN9CnV5/es"];
// File names of the first 3 images which will be added to DOM at page load
const init = [images[0], images[1], images[2]];
/**
* Generate one or more <img>s from a given array/
* #param {Array} array - An array of file names
* #param {String|Object} node - Either a selector string or a DOM object referenced to
* be the elemment to append the <img>s to.
* #param {String} css - A className to be assigned to each <img> #default
* is "img"
* #returns {array} - An array of <img>
*/
function genImg(array, node, css = "img") {
let root = typeof node === "string" ?
document.querySelector(node) : node ?
node : document.body;
let offset = root.childElementCount;
const pix = array.flatMap((img, idx) => {
if (idx >= offset) {
const image = new Image();
const frame = document.createElement("figure");
image.src = `https://i.ibb.co/${img}.png`;
image.className = css;
image.dataset.idx = offset + idx;
root.append(frame.appendChild(image));
return image;
}
return [];
});
return pix;
}
const main = document.forms.gallery;
const io = main.elements;
const areas = Array.from(io.area);
const imgsA = genImg(init, areas[0]);
const imgsB = genImg(init, areas[1]);
imgsA.forEach(img => img.onclick = highlightA);
function highlightA(event) {
this.classList.toggle("highlight");
}
areas[1].onclick = highlightB;
function highlightB(event) {
const clk = event.target;
if (clk.matches("img")) {
clk.classList.toggle("highlight");
}
}
const btns = Array.from(io.add);
btns.forEach(btn => btn.onclick = addImg);
function addImg(event) {
const clk = event.target;
if (clk.matches("button")) {
let idx = btns.indexOf(clk);
genImg(images, areas[idx]);
}
}
const bind = io.bind;
bind.onclick = bindImg;
function bindImg(event) {
Array.from(document.querySelectorAll("#A img"))
.forEach(img => img.onclick = highlightA);
}
html {font: 300 4vmin/1.15 "Segoe UI"}
form {display: flex; flex-flow: column nowrap; justify-content: center;
margin: 15px auto; padding: 0 10px;}
fieldset {margin: 0.5rem 0}
fieldset fieldset {display: flex; justify-content: space-evenly; align-items: center;}
legend {font-size: 1.25rem}
button {font: inherit; float: right; cursor: pointer;}
figure {display: inline-flex; justify-content: center; align-items: center;
margin: 0.5rem 0.5rem 0; padding: 0.5rem;}
.img {display:inline-block; max-width: 5rem}
.highlight {outline: 5px groove cyan;}
<form id="gallery">
<fieldset>
<legend>Area A</legend>
<fieldset id="A" name="area"></fieldset>
<button name="add" type="button">ADD</button>
<button name="bind" type="button">BIND</button>
</fieldset>
<hr>
<fieldset>
<legend>Area B</legend>
<fieldset id="B" name="area"></fieldset>
<button name="add" type="button">ADD</button>
</fieldset>
</form>

Is there a way to have an add event listener first execute a mousedown, then mouseover, and finally mouseup?

So right now I have a 20 by 20 grid and I want the user to be able to click and select multiple cells in the grid. There is a method I was able to find online but the problem is that mouseover takes over and highlights the cells right when the mouse is over the cells and this is not what I want. I want the user click on a cells then basically drag their mouse and highlight the cells that they want then execute mouseup once they let go.
These are my files.
let graph = document.getElementById("container");
graph.style.display = "flex";
function createGraph() {
let j = 0;
for (let i = 0; i < 20; i++) {
let row = document.createElement("div");
row.id = "row" + i;
row.style.height = "50px";
row.style.width = "50px";
graph.appendChild(row);
let currentRow = document.getElementById("row" + i);
j++;
for (let j = 0; j < 20; j++) {
let cell = document.createElement("div");
cell.classList.add("cells");
///id's are used later in the project
cell.id = "index" + j + i;
cell.style.border = "1px solid black";
cell.style.height = "50px";
cell.style.width = "50px";
currentRow.appendChild(cell);
}
}
}
createGraph();
function main() {
document.querySelectorAll(".cells").forEach(item => {
["mousedown", "mouseover", "mouseup"].forEach(function(e) {
item.addEventListener(e, function() {
item.style.backgroundColor = "red";
})
})
})
}
main();
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
So in the main function I added an even listener to all the cells and I am trying to change their color to red. The problem is that the mouseover event takes over the mousedown which is what I want to happen first. How can I make it so the user is able to first click down on a cell then drag their mouse and keep highlighting cells and once they let go of the mouse the highlighting stops. Is there away to first execute the mousedown, then mouseover and finally the mouseup?
I refactored your code a little. Here is a simple example how you can use toggle state:
let graph = document.getElementById('container');
function createGraph() {
for (let i = 0; i < 20; i++) {
let row = document.createElement('div');
row.id = 'row' + i;
row.className = 'rows';
for (let j = 0; j < 20; j++) {
let cell = document.createElement('div');
cell.className = 'cells';
cell.id = 'index' + j + i;
row.appendChild(cell);
}
graph.appendChild(row);
}
}
createGraph();
function main() {
let canSelect = false;
document.addEventListener('mousedown', () => canSelect = true);
document.addEventListener('mouseup', () => canSelect = false);
document.querySelectorAll('.cells').forEach(item => {
['mousedown', 'mouseover'].forEach(function(e) {
item.addEventListener(e, () => {
if (!canSelect && e !== 'mousedown') return;
item.style.backgroundColor = 'red';
})
})
})
}
main();
#container {
display: flex;
}
.rows, .cells {
width: 50px;
height: 50px;
}
.cells {
border: 1px solid black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
The trick with things like this is to only add the mouseover event on mousedown to begin with. mouseover is generally an expensive event to have anyways (because it fires a lot), so you only "turn it on" when you want and remove it when you don't.
Also, if you're hooking the same event to multiple elements in the same parent, it is far better to assign the event to the parent and then check the target and act when it is one of the children you want (usually using the .matches() method).
Then, you don't have to worry about mousemove firing first, because it'll always fire second. Just be aware it'll probably fire MANY times per cell, so you need to write your code to handle that.
let targetElements = [];
const parent = document.querySelector('.parent');
const mouseoverHandler = ({ target }) => {
if (!target.matches('.parent span')
|| targetElements.includes(target)) {
return;
}
targetElements.push(target);
};
parent.addEventListener('mousedown', ({ target }) => {
// use whatever selector makes sense for your children
if (!target.matches('.parent span')) return;
// reset the list here in case they mouseup-ed outside of the parent
targetElements = [];
// turn mouseover "on"
parent.addEventListener('mouseover', mouseoverHandler);
targetElements.push(target);
console.log('mouseover on');
});
parent.addEventListener('mouseup', ({ target }) => {
// use whatever selector makes sense for your children
if (!event.target.matches('.parent span')) return;
// turn mouseover "off"
parent.removeEventListener('mouseover', mouseoverHandler);
// do something with them
targetElements.forEach(el => el.classList.toggle('on'));
console.log('mouseover off');
});
.parent {
border: 2px solid #333;
width: 150px;
display: flex;
flex-wrap: wrap;
}
.parent span {
flex: 0 0 50px;
flex-wrap: wrap;
height: 50px;
border: 1px solid #CCC;
margin: -1px;
height: 50px;
display: -block;
}
.parent span:hover {
/* doesn't seem to work in the demo window */
background: #EEC;
cursor: pointer;
}
.parent span.on {
background: #F00;
}
<div class="parent">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>

Background Image Shouldn't Appear In Header In Drop Down Menu

I've made a JavaScript dropdown menu. Everything works fine, except the background image. I have the image set to change when the dropdown menu is expanded, which also works fine.
The issue is with the headers. Unless the header is set to display inline-block or inline, the menu won't expand. When set to inline-block or inline everything expands when you click on the box. But if you click on the header itself, it adds the padding and border around the header and ads in the background image from the div. How do you prevent this from happening?
<div class="panel">
<div class="collapse"><h2>Features</h2></div>
<div class="elements">
text<br>text<br>text
</div>
</div>
<style>
h2 {/*display: inline-block;*/
/*display: inline;*/
margin: 0px;
padding: 0px 0px 0px 0px;
color: #ffffff;
text-align: center;
text-transform: uppercase;}
.expand,
.collapse {cursor: pointer;
background-position: center right;
background-repeat: no-repeat;
background-color: #000033;
border: 2px solid #990044;
color: #ffffff;
padding: 10px 0px;
text-align: center;}
.collapse {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAABHUlEQVRIS+3USw6CMBAA0BYNutOjcAQ9iXHjhoXhBt4AEmwwbvQm6g04ii4hCLaNNRUp/dDgQllRPvMy05lC0PMFe/bAH7Re8R8qaYySTZGPoyBYXm3WMQwP04Gbhfd8FJDYtKTxNjkCCBf4Ni3y0dwWSrChm51wXI/FhjHaRXix5rKygtYwGr4C1QUitPdKUJ7xemILbcJw7JsDnBktqU20DfP9VfoaCxuoDCPJvc1hF1QF+wBNy6uKNYK6qA4mBFVRXawVlKHkPTfUbKJo65NuFJ1W0sNb1EjPgOQEUcakGbJIApRPQpoZ+1iaoQKqjCln2IJqYdpgrZGArEGaGke5pPzPZE/Juq0bjbtU9KPpc6MMTTGjPeyCfQV8AK4c2lwJRjQ3AAAAAElFTkSuQmCC);}
.expand {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAABD0lEQVRIS+3Wyw2CQBAG4F1Q8KadSAnYgR0YL164SCWYKDHxonZgB1ICdiI3QR6yCgSVXXaAkGDkwgXyZf6dYcCo5Qu37KFugIaxH5FkdH1+hSYErpBgPck9E8j35AkUBYE5TEkqs6EoN1iApWmCUC6QgYHRUpCCOYk0zDUNV6VMkIYJSFAJFKLQim8glAqyME1b2AQ0zZ0CRQtBHiyNEop+gRCsCvoGVsGgaAbWwSDoE2wC40VxkxgPiteb7QFhPMsNsEPmLG196DZgolF0fFXYd614M47jhxvBCtEIXfy7rGZnKEq3k4jEZd3KPhMhcxqgYBV4gylZZaXf0qqR0t77g00n2pG/tjpl/37TPACe/d8VUJ3+EgAAAABJRU5ErkJggg==);}
.elements {background-color: #ccd9ff;
overflow: hidden;}
</style>
<script>
function aaManageEvent (eventObj, event, eventHandler) {
if (eventObj.addEventListener) {eventObj.addEventListener (event, eventHandler, false);}
else if (eventObj.attachEvent) {event = "on" + event; eventObj.attachEvent (event, eventHandler);}
}
window.onload = function () {
var divs = document.getElementsByTagName ("div");
for (var i = 0; i < divs.length; i++) {
if (divs[i].className == "collapse") {
aaManageEvent (divs [i], "click", spring.expandOrCollapse);
}
else if (divs[i].className == "elements") {
var height = divs [i].offsetHeight;
divs [i] .height = height;
if (divs [i] .id == "") divs [i].id = "div" + i;
divs [i].style.height = "0";
}
}
}
var spring = {
// adjust height
adjustItem : function (val, newItem) {
document.getElementById (newItem).style.height = val + "px";
},
// check if expand or collapse
expandOrCollapse : function (evnt) {
evnt = evnt ? evnt : window.event;
var target = evnt.target ? evnt.target : evnt.srcElement;
if (target.className == "collapse") spring.expand (target);
else spring.collapse (target);
},
// Expand Panel
expand : function (target) {
target.className = 'expand';
var children = target.parentNode.childNodes, panel;
for (var i = 0; i < children.length; i++) {
if (children [i].className == "elements") {
panel = children [i]; break;
}
}
var height = panel.height, incr = height / 20;
for (var i=0; i < 20; i++) {
var val = (i + 1) * incr;
var func = "spring.adjustItem (" + val + ", '" + panel.id + "')";
setTimeout (func, (i + 1) * 30);
}
},
// Collapse Panel
collapse : function (target) {
target.className = "collapse";
var children = target.parentNode.childNodes, panel;
for (var i = 0; i < children.length; i++) {
if (children [i].className == "elements") {
panel = children [i]; break;
}
}
var height = panel.height, decr = height / 20;
for (var i = 0; i < 20; i++) {
var val = height - (decr * (i + 1));;
var func = "spring.adjustItem (" + val + ", '" + panel.id + "')";
setTimeout (func, (i + 1) * 30);
}
}
};
</script>
When I click on the div, the dropdown works. But when I click on the header, I see an error in the browser console.
I think because when clicking on the <h2>Features</h2> element, the click event bubbles up to the <div class="collapse">, making the var target in this line not the <div class="collapse"> but the <h2>:
var target = evnt.target ? evnt.target : evnt.srcElement;
A possible solution to fix this is for example to add an id to this line:
<div id="header" class="collapse"><h2>Features</h2></div>
Then you can directly get that div by id and change the classname.
I've adjusted your expandOrCollapse function to make it toggle based on the classname from the div with id="header".
For example:
function aaManageEvent (eventObj, event, eventHandler) {
if (eventObj.addEventListener) {eventObj.addEventListener (event, eventHandler, false);}
else if (eventObj.attachEvent) {event = "on" + event; eventObj.attachEvent (event, eventHandler);}
}
window.onload = function () {
var divs = document.getElementsByTagName ("div");
for (var i = 0; i < divs.length; i++) {
if (divs[i].className == "collapse") {
aaManageEvent (divs [i], "click", spring.expandOrCollapse);
}
else if (divs[i].className == "elements") {
var height = divs [i].offsetHeight;
divs [i] .height = height;
if (divs [i] .id == "") divs [i].id = "div" + i;
divs [i].style.height = "0";
}
}
}
var spring = {
// adjust height
adjustItem : function (val, newItem) {
document.getElementById (newItem).style.height = val + "px";
},
// check if expand or collapse
expandOrCollapse : function (evnt) {
var header = document.getElementById('header');
if (header.className === "collapse") {
spring.expand(header);
} else {
spring.collapse(header);
}
},
// Expand Panel
expand : function (target) {
target.className = 'expand';
var children = target.parentNode.childNodes, panel;
for (var i = 0; i < children.length; i++) {
if (children [i].className == "elements") {
panel = children [i]; break;
}
}
var height = panel.height, incr = height / 20;
for (var i=0; i < 20; i++) {
var val = (i + 1) * incr;
var func = "spring.adjustItem (" + val + ", '" + panel.id + "')";
setTimeout (func, (i + 1) * 30);
}
},
// Collapse Panel
collapse : function (target) {
target.className = "collapse";
var children = target.parentNode.childNodes, panel;
for (var i = 0; i < children.length; i++) {
if (children [i].className == "elements") {
panel = children [i]; break;
}
}
var height = panel.height, decr = height / 20;
for (var i = 0; i < 20; i++) {
var val = height - (decr * (i + 1));;
var func = "spring.adjustItem (" + val + ", '" + panel.id + "')";
setTimeout (func, (i + 1) * 30);
}
}
};
h2 {/*display: inline-block;*/
/*display: inline;*/
margin: 0px;
padding: 0px 0px 0px 0px;
color: #ffffff;
text-align: center;
text-transform: uppercase;}
.expand,
.collapse {cursor: pointer;
background-position: center right;
background-repeat: no-repeat;
background-color: #000033;
border: 2px solid #990044;
color: #ffffff;
padding: 10px 0px;
text-align: center;}
.collapse {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAABHUlEQVRIS+3USw6CMBAA0BYNutOjcAQ9iXHjhoXhBt4AEmwwbvQm6g04ii4hCLaNNRUp/dDgQllRPvMy05lC0PMFe/bAH7Re8R8qaYySTZGPoyBYXm3WMQwP04Gbhfd8FJDYtKTxNjkCCBf4Ni3y0dwWSrChm51wXI/FhjHaRXix5rKygtYwGr4C1QUitPdKUJ7xemILbcJw7JsDnBktqU20DfP9VfoaCxuoDCPJvc1hF1QF+wBNy6uKNYK6qA4mBFVRXawVlKHkPTfUbKJo65NuFJ1W0sNb1EjPgOQEUcakGbJIApRPQpoZ+1iaoQKqjCln2IJqYdpgrZGArEGaGke5pPzPZE/Juq0bjbtU9KPpc6MMTTGjPeyCfQV8AK4c2lwJRjQ3AAAAAElFTkSuQmCC);}
.expand {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAABD0lEQVRIS+3Wyw2CQBAG4F1Q8KadSAnYgR0YL164SCWYKDHxonZgB1ICdiI3QR6yCgSVXXaAkGDkwgXyZf6dYcCo5Qu37KFugIaxH5FkdH1+hSYErpBgPck9E8j35AkUBYE5TEkqs6EoN1iApWmCUC6QgYHRUpCCOYk0zDUNV6VMkIYJSFAJFKLQim8glAqyME1b2AQ0zZ0CRQtBHiyNEop+gRCsCvoGVsGgaAbWwSDoE2wC40VxkxgPiteb7QFhPMsNsEPmLG196DZgolF0fFXYd614M47jhxvBCtEIXfy7rGZnKEq3k4jEZd3KPhMhcxqgYBV4gylZZaXf0qqR0t77g00n2pG/tjpl/37TPACe/d8VUJ3+EgAAAABJRU5ErkJggg==);}
.elements {background-color: #ccd9ff;
overflow: hidden;}
<div class="panel">
<div id="header" class="collapse"><h2>Features</h2></div>
<div class="elements">
text<br>text<br>text
</div>
</div>
Edit your declaration block of h2 as shown below. This will solve your problem.
h2 {
display: inline-block;
margin: 0px;
padding: 0px 0px 0px 0px;
color: #ffffff;
text-align: center;
text-transform: uppercase;
pointer-events: none; // this line solves your problem.
}
CSS property pointer-events let you control under what circumstances an element can become the target of mouse events. when you set it to none, the element will never be the target of mouse events. So, the click event passed on to its descendant elements (here the box).

List elements animation doesn't work when hide/show

Is it possible for li elements animation from here:
http://jsfiddle.net/8XM3q/light/
to animate when there is show/hide function used instead of remove?
When i have changed "remove" to "hide" elements didn't move: http://jsfiddle.net/8XM3q/90/
I wanted to use this function for my content filtering animations - thats why i have to replace "remove" to "hide/show".
I'm not good at JS but i think that it counts all elements, even when they are hidden:
function createListStyles(rulePattern, rows, cols) {
var rules = [], index = 0;
for (var rowIndex = 0; rowIndex < rows; rowIndex++) {
for (var colIndex = 0; colIndex < cols; colIndex++) {
var x = (colIndex * 100) + "%",
y = (rowIndex * 100) + "%",
transforms = "{ -webkit-transform: translate3d(" + x + ", " + y + ", 0); transform: translate3d(" + x + ", " + y + ", 0); }";
rules.push(rulePattern.replace("{0}", ++index) + transforms);
}
}
var headElem = document.getElementsByTagName("head")[0],
styleElem = $("<style>").attr("type", "text/css").appendTo(headElem)[0];
if (styleElem.styleSheet) {
styleElem.styleSheet.cssText = rules.join("\n");
} else {
styleElem.textContent = rules.join("\n");
}
So my question is how to adapt that part of code to count only "show" (displayed) elements?
If you want to have the animation and still have all of the data then use detach() function instead of remove: jQuery - detach
And to count or select elements try to do this using css's class attached to each element.
I edited your jsFiddle:
http://jsfiddle.net/8XM3q/101/
notice that I changed this line:EDIT: http://jsfiddle.net/8XM3q/101/
$(this).closest("li").remove();
to this:
$(this).closest("li").hide("slow",function(){$(this).detach()});
This means hide the item, speed = slow, when done hiding remove it.
Hope this is what you meant.
EDIT: Included detach.
As per your comment:
I wanted to use this function for my content filtering animations -
thats why i have to replace "remove" to "hide/show" I don't want to
remove elements at all. Im sorry if I mislead You with my question.
What you can do is to use a cache to store the list-items as they are hidden when you do the content filtering. Later when you need to reset the entire list, you can replenish the items from the cache.
Relevant code fragment...
HTML:
...
<button class="append">Add new item</button>
<button class="replenish">Replenish from cache</button>
<div id="cache"></div>
JS:
...
$(this).closest("li").hide(600, function() {
$(this).appendTo($('#cache'));
});
...
$(".replenish").click(function () {
$("#cache").children().eq(0).appendTo($(".items")).show();
});
Demo Fiddle: http://jsfiddle.net/abhitalks/8XM3q/102/
Snippet:
$(function() {
$(document.body).on("click", ".delete", function (evt) {
evt.preventDefault();
$(this).closest("li").hide(600, function() {
$(this).appendTo($('#cache'));
});
});
$(".append").click(function () {
$("<li>New item <a href='#' class='delete'>delete</a></li>").insertAfter($(".items").children()[2]);
});
$(".replenish").click(function () {
$("#cache").children().eq(0).appendTo($(".items")).show();
});
// Workaround for Webkit bug: force scroll height to be recomputed after the transition ends, not only when it starts
$(".items").on("webkitTransitionEnd", function () {
$(this).hide().offset();
$(this).show();
});
});
function createListStyles(rulePattern, rows, cols) {
var rules = [], index = 0;
for (var rowIndex = 0; rowIndex < rows; rowIndex++) {
for (var colIndex = 0; colIndex < cols; colIndex++) {
var x = (colIndex * 100) + "%",
y = (rowIndex * 100) + "%",
transforms = "{ -webkit-transform: translate3d(" + x + ", " + y + ", 0); transform: translate3d(" + x + ", " + y + ", 0); }";
rules.push(rulePattern.replace("{0}", ++index) + transforms);
}
}
var headElem = document.getElementsByTagName("head")[0],
styleElem = $("<style>").attr("type", "text/css").appendTo(headElem)[0];
if (styleElem.styleSheet) {
styleElem.styleSheet.cssText = rules.join("\n");
} else {
styleElem.textContent = rules.join("\n");
}
}
createListStyles(".items li:nth-child({0})", 50, 3);
body { font-family: Arial; }
.items {
list-style-type: none; padding: 0; position: relative;
border: 1px solid black; height: 220px; overflow-y: auto; overflow-x: hidden;
width: 600px;
}
.items li {
height: 50px; width: 200px; line-height: 50px; padding-left: 20px;
border: 1px solid silver; background: #eee; box-sizing: border-box; -moz-box-sizing: border-box;
position: absolute; top: 0; left: 0;
-webkit-transition: all 0.2s ease-out; transition: all 0.2s ease-out;
}
div.cache { display: none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="items">
<li>Monday delete
</li><li>Tuesday delete
</li><li>Wednesday delete
</li><li>Thursday delete
</li><li>Friday delete
</li><li>Saturday delete
</li><li>Sunday delete</li>
</ul>
<button class="append">Add new item</button>
<button class="replenish">Replenish from cache</button>
<div id="cache"></div>
EDIT: There is a simpler way without adding any classes, is to use the :visible selector
You need to understand a concept is Javascript, which is that functions are considered objects. You can pass a function to another function, or return a function from a function.
Let's check the documentation on jQuery for the hide function
.hide( duration [, easing ] [, complete ] )
It says that it accepts a function as an argument for complete, which is called when the hide animation is complete.
The function hide does not remove the element from the DOM but simply "hides" it as the name suggests. So what we want to do, is hide the element then when the animation of hiding is done, we add a class "removed" to the list element.
We will accomplish that by passing a function (complete argument) like so :
$(this).closest("li").hide(400, function() {
$(this).addClass('removed');
});
When you want to select the list elements that are not "removed", use this selector $('li:not(.removed)')

Scroll to position WITHIN a div (not window) using pure JS

PURE JS ONLY PLEASE - NO JQUERY
I have a div with overflow scroll, the window (html/body) never overflows itself.
I have a list of anchor links and want to scroll to a position when they're clicked.
Basically just looking for anchor scrolling from within a div, not window.
window.scrollTo etc. don't work as the window never actually overflows.
Simple test case http://codepen.io/mildrenben/pen/RPyzqm
JADE
nav
a(data-goto="#1") 1
a(data-goto="#2") 2
a(data-goto="#3") 3
a(data-goto="#4") 4
a(data-goto="#5") 5
a(data-goto="#6") 6
main
p(data-id="1") 1
p(data-id="2") 2
p(data-id="3") 3
p(data-id="4") 4
p(data-id="5") 5
p(data-id="6") 6
SCSS
html, body {
width: 100%;
height: 100%;
max-height: 100%;
}
main {
height: 100%;
max-height: 100%;
overflow: scroll;
width: 500px;
}
nav {
background: red;
color: white;
position: fixed;
width: 50%;
left: 50%;
}
a {
color: white;
cursor: pointer;
display: block;
padding: 10px 20px;
&:hover {
background: lighten(red, 20%);
}
}
p {
width: 400px;
height: 400px;
border: solid 2px green;
padding: 30px;
}
JS
var links = document.querySelectorAll('a'),
paras = document.querySelectorAll('p'),
main = document.querySelector('main');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(){
var linkID = this.getAttribute('data-goto').slice(1);
for (var j = 0; j < links.length; j++) {
if(linkID === paras[j].getAttribute('data-id')) {
window.scrollTo(0, paras[j].offsetTop);
}
}
})
}
PURE JS ONLY PLEASE - NO JQUERY
What you want is to set the scrollTop property on the <main> element.
var nav = document.querySelector('nav'),
main = document.querySelector('main');
nav.addEventListener('click', function(event){
var linkID,
scrollTarget;
if (event.target.tagName.toUpperCase() === "A") {
linkID = event.target.dataset.goto.slice(1);
scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
main.scrollTop = scrollTarget.offsetTop;
}
});
You'll notice a couple of other things I did different:
I used event delegation so I only had to attach one event to the nav element which will more efficiently handle clicks on any of the links.
Likewise, instead of looping through all the p elements, I selected the one I wanted using an attribute selector
This is not only more efficient and scalable, it also produces shorter, easier to maintain code.
This code will just jump to the element, for an animated scroll, you would need to write a function that incrementally updates scrollTop after small delays using setTimeout.
var nav = document.querySelector('nav'),
main = document.querySelector('main'),
scrollElementTo = (function () {
var timerId;
return function (scrollWithin, scrollTo, pixelsPerSecond) {
scrollWithin.scrollTop = scrollWithin.scrollTop || 0;
var pixelsPerTick = pixelsPerSecond / 100,
destY = scrollTo.offsetTop,
direction = scrollWithin.scrollTop < destY ? 1 : -1,
doTick = function () {
var distLeft = Math.abs(scrollWithin.scrollTop - destY),
moveBy = Math.min(pixelsPerTick, distLeft);
scrollWithin.scrollTop += moveBy * direction;
if (distLeft > 0) {
timerId = setTimeout(doTick, 10);
}
};
clearTimeout(timerId);
doTick();
};
}());
nav.addEventListener('click', function(event) {
var linkID,
scrollTarget;
if (event.target.tagName.toUpperCase() === "A") {
linkID = event.target.dataset.goto.slice(1);
scrollTarget = main.querySelector('[data-id="' + linkID + '"]');
scrollElementTo(main, scrollTarget, 500);
}
});
Another problem you might have with the event delegation is that if the a elements contain child elements and a child element is clicked on, it will be the target of the event instead of the a tag itself. You can work around that with something like the getParentAnchor function I wrote here.
I hope I understand the problem correctly now: You have markup that you can't change (as it's generated by some means you have no control over) and want to use JS to add functionality to the generated menu items.
My suggestion would be to add id and href attributes to the targets and menu items respectively, like so:
var links = document.querySelectorAll('a'),
paras = document.querySelectorAll('p');
for (var i = 0; i < links.length; i++) {
links[i].href=links[i].getAttribute('data-goto');
}
for (var i = 0; i < paras.length; i++) {
paras[i].id=paras[i].getAttribute('data-id');
}

Categories

Resources