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');
}
Related
I have a 16x16 grid of small squares. I have added a permanent "hover" effect to make the very first box turn red when I put my mouse over it. However, I want to add the same effect to all of the boxes on the page. I can't figure out how to do it - I have tried to add an event listener to the whole page and used target.nodeName and target.NodeValue, but to no avail. I have included the working version where the fix box turns red on mouseover.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
The immediate problem you are having is that this is only querying, and subsequently adding an event listener to, one element.
const smallBox = document.querySelector('.smallBox');
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
In the above portion of your code, querySelector only returns the first matching element. You may be looking for querySelectorAll here which returns a NodeList of matching elements.
You have two options (perhaps others if you want to restructure your code further). The naive approach is to, in fact, query for all of the cells and add event listeners to each of them.
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(smallBox => {
smallBox.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
});
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
Another option is to use event delegation as you identified. Here is how you can leverage that. Note: this approach is a bit tricker for an aggressive event like "mouseover" as you may get false positive targets (like the outer container for example).
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
bigContainer.addEventListener('mouseover', e => {
var target = e.target
if (target !== bigContainer) {
target.classList.add('permahover')
}
})
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You need to use a delegation event, because all the small boxes don't exist on the page when the page is loaded (You can figure out in the inspector element that only your first box has the event listener).
So you listen the whole container (because it is always on the page on load)
bigContainer.addEventListener('mouseover', () => {
// Code for checking if we hovered a small div & if yes applying the style
});
...and then do a comparaison with the event.target (which will be the small div hovered)
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
var n=16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for(var i = 1; i < n; i++) {
bigContainer.innerHTML+='<div class="row">';
for(j = 0; j < n; j++) {
bigContainer.innerHTML+='<div class="smallBox">';
}
}
const smallBox = document.querySelector('.smallBox');
bigContainer.addEventListener('mouseover', () => {
if (event.target.matches('.smallBox')) {
event.target.classList.add('permahover');
}
});
.smallBox {
border: 1px solid black;
width: 20px;
height: 20px;
display: inline-block;
}
.permahover {
background: red;
}
h1 {
text-align: center;
}
.bigContainer {
text-align: center;
}
<h1>Etch-a-Sketch Assignment - The Odin Project</h1>
<div class="bigContainer">
</div>
You can use forEach method to loop through all boxes and add eventListener on each one.
If all of them have .smallBox class you can do it like this:
const smallBoxes = document.querySelectorAll('.smallBox');
smallBoxes.forEach(box => box.addEventListener('mouseover', () => {
smallBox.classList.add('permahover');
}))
I hope it helped you!
let smallBoxes = document.querySelectorAll('.smallBox');
[...smallBoxes].forEach(el => {
el.addEventListener('mouseover', e => e.target.classList.add('permahover'));
});
you should set the eventlistener to your DOM and ask if the trigger element are one of your elements which are that specific class. So you can handle every element with that class.
var n = 16; //take grid column value as you want
const bigContainer = document.querySelector('.bigContainer')
for (var i = 1; i < n; i++) {
bigContainer.innerHTML += '<div class="row">';
for (j = 0; j < n; j++) {
bigContainer.innerHTML += '<div class="smallBox">';
}
}
document.addEventListener('mouseover', function(e) {
if (e.target && e.target.className == 'smallBox') {
var target = e.target;
target.classList.add('permahover');
}
});
Working js fiddle: https://jsfiddle.net/nwukf205/
hope i could help you :)
if you got questions just ask
Have you tried the :hover selector? Not sure if you want specify any dynamic actions here, but it's easy to do basic stuff.
https://www.w3schools.com/cssref/sel_hover.asp
a:hover {
background-color: yellow;
}
I haven't tried your example myself but something similar to this has been answered here:
Hover on element and highlight all elements with the same class
I'm trying to fade in/out and fix the blue div on the left when scrolled relative to the image blocks on the right.
http://www.warface.co.uk/#/testing/
pass: squared
.meta { /*This is the block I'm trying to stick/*
background: blue;
position: fixed;
width: 372px;
float: left;
z-index: 3;
right: 100%;
}
Here is the basics in JavaScript:
function controlMeta() {
var meta = document.querySelector("div.meta");
console.log(meta);
if (window.scrollY > 500) {
meta.style.display = "none";
} else {
meta.style.display = "block";
}
}
window.addEventListener("scroll", function () {
controlMeta();
})
You can get your elements scroll position with something like this:
document.getElementById("57513a9220c6475fb77061c5").getBoundingClientRect().top+window.scrollY
EDIT 1
Here is a method for associating elements with the meta box, based upon the previous:
//Load elements that affect the meta box
var meta = [];
var images = document.querySelector('.sqs-gallery').getElementsByTagName('img');
for (var i = 0; i < images.length; i++) {
meta.push({
node : images[i],
height : images[i].height,
//top is used with scroll position to determine which element we are looking at
top : images[i].getBoundingClientRect().top + window.scrollY
});
}
function controlMeta() {
meta.filter(function (el) {
//You might need to pad your filter a little
return window.scrollY < el.top && window.scrollY > el.top - el.height;
}).forEach(function (el) {
//These are the matching elements. I'm just fetching the src as an example
document.querySelector("div.meta div.body").innerHTML = el.node.src;
});
}
window.addEventListener("scroll", function () {
controlMeta();
});
I'm trying to create my own "autocomplete", but when I type a letter (eg. w for word), then there's a splitsecond delay - enough to annoy the eye.
Here's my testcode:
CSS:
#txtSearchAutocomplete {
background-color: white !important;
position: absolute;
top: 0;
width: 100%;
font-size: 20px !important;
border: none !important;
color: gray;
}
#txtSearch {
background-color: transparent !important;
position: absolute;
top: 0;
width: 100%;
font-size: 20px !important;
border: none !important;
}
HTML:
<span style="position: relative; display: inline-block; width:100%; top: -18px;">
<input type="text" id="txtSearchAutocomplete" disabled >
<input type="text" id="txtSearch">
</span>
JS:
$(document).ready(function($) {
$("#txtSearch").focus();
$("#txtSearch").keyup(function(e) {
var autocomplete = ['word', 'excel'];
var $txtAutocomplete = $("#txtSearchAutocomplete");
var txt = $("#txtSearch").val().trim().toLowerCase();
$txtAutocomplete.val("");
if (txt == "") return;
for (i = 0; i < autocomplete.length; i++) {
var entry = autocomplete[i];
if (entry.indexOf(txt) == 0) {
$txtAutocomplete.val(entry);
break;
};
};
});
});
And a fiddle sample:
https://jsfiddle.net/25gwz1qu/1/
If you type in the letter w - delete it - type it again and so on, then you will notice a small delay. It might seam that the delay is a bit longer in IE.
Any idea how to get rid of this delay?
Thanks
The reason for the delay you are seeing is because the event triggers once the user lets go of the key. In that case, the oninput is the way to go. The event triggers when the textbox input changes.
$("#txtSearch").on('input', function(e) { ... })
Please take a look on my solution with comments that explain why I did those changes and here is a Working Fiddle.
On my machine the auto-complete is almost instant after those modifications.
$(document).ready(function($) {
// i had moved all selectors outside the function so the havy dom selection will happen only once
var autocomplete = ['word', 'excel'];
var $txtAutocomplete = $("#txtSearchAutocomplete");
var $searchElement = $("#txtSearch");
$searchElement.focus();
// In Jquery on works faster than on key up, cause user lets go of the key.
$searchElement.on('input',function(e) {
var txt = $searchElement.val().trim().toLowerCase();
// I had replaced the element to be a div and not a input cause the div element is much light weight and faster to draw for the browser
$txtAutocomplete.text("");
if (txt == "")
return;
for (i = 0; i < autocomplete.length; i++) {
var entry = autocomplete[i];
if (entry.indexOf(txt) == 0) {
$txtAutocomplete.text(entry);
break;
};
};
});
});
try this,
$(document).ready(function($) {
$("#txtSearch").focus();
$("#txtSearch").on('input',function(e) {
var autocomplete = ['word', 'excel'];
var $txtAutocomplete = $("#txtSearchAutocomplete");
var txt = $("#txtSearch").val().trim().toLowerCase();
$txtAutocomplete.val("");
if (txt == "") return;
for (i = 0; i < autocomplete.length; i++) {
var entry = autocomplete[i];
if (entry.indexOf(txt) == 0) {
$txtAutocomplete.val(entry);
break;
};
};
});
});
I'm trying to update a set of divs class="oct_days" to give them id based on :nth-child(n). The format of the id is oct_n. I'm trying to accomplish this using a for loop to set this for divs.
window.onload = function addOctDate() {
var cls = document.getElementByClass("oct_days");
for (var n = 1; n < 32; n++) {
cls[n].id = "oct_" + n;
}
};
Fiddle (http://jsfiddle.net/ascottz/D9Exm/)
The idea is to have .oct_days:nth-child(1) have id="oct_1", but id isn't being set.
clsyour issues are this:
window.onload was being run before your html was initialized
you need to call document.getElementsByClassName not
you are starting your iteration at 1, indexes are 0 based and you should start there and add the + 1 as noted below
also, while iterating, its good to only iterate only over the known items in your list
try this code:
function addOctDate() {
var cls = document.getElementsByClassName("oct_days");
for (n=0, length = cls.length; n < length; n++) {
cls[n].id= "oct_" + (n + 1);
}
};
addOctDate()
The function is getElementsByClassName.
The fiddle doesn't work because you're seeing window.onload while your code is already being run inside that event (the dropdown on the left says onLoad). It'll also error out because you don't have 31 elements in the HTML, but it'll still set the IDs.
Your code is very simple to fix
(function () {
// .getElementsByClassName not .getElementByClass
var cls = document.getElementByClassName("oct_days"),
// set the stopping point DYNAMICALLY
len = cls.length,
// start the index at 0;
n = 0;
for (; n < len; n++) {
cls[n].id = "oct_" + (n + 1);
}
// ()(); auto runs the function
})();
Here is a way to add ids to elements and classes using just plain js.
HTML
<div id="test">
Content will append below!
<input type="button" value="click me!" onClick="myFunction();"/>
</div>
CSS
.cool_0 {
background: red;
height: 200px;
width: 100%;
}
.cool_1 {
background: yellow;
height: 200px;
width: 100%;
}
.cool_2 {
background: red;
height: 200px;
width: 100%;
}
.cool_3 {
background: yellow;
height: 200px;
width: 100%;
}
.cool_4 {
background: red;
height: 200px;
width: 100%;
}
.cool_5 {
background: yellow;
height: 200px;
width: 100%;
}
JS
function myFunction(){
var myId = 0;
var counter = 0;
var myDiv = document.getElementById("test")
for(var i = 0; i < 5; i++){
var textNode = document.createTextNode("sup! My current id is "+myId+" !")
var t = document.createElement("div");
t.setAttribute("id", counter++)
t.setAttribute("class", "cool_"+myId++)
t.appendChild(textNode)
myDiv.appendChild(t);
}
}
I'm new to JavaScript but moving over from ActionScript, so I'm using a lot of AS3 logic and not sure what's possible and not.
I have a series of 5 dots for an image slider nav. The dots are just CSS styled dots, so I'm trying to make it so I can control the colors using element.style.backgroundColor.
Here's my script:
function btnFeatured(thisBtn) {
btnFeatured_reset();
for (i = 1; i <= 5; i++) {
if (thisBtn === document.getElementById("dotFeat" + i)) {
document.getElementById("dotFeat" + i).style.backgroundColor = "#ffae00";
}
}
}
function btnFeatured_reset() {
for (i = 1; i <= 5; i++) {
document.getElementById("dotFeat" + i).style.backgroundColor = "#969696";
}
}
Seems to work just fine, but when I click the dot, it turns orange (ffae00) and then immediately turns back to gray (969696).
And just in case, here's the style I'm using for the dots:
#featured-nav a {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 8px;
background-color: #969696;
border-bottom: none;
margin: 0 14px;
}
#featured-nav a:hover {
background-color: #ffae00;
border-bottom: none;
}
And my html:
Change the HTML to
test
test
test
test
test
and the JS:
function btnFeatured(thisBtn) {
for (i = 1; i <= 5; i++) {
var state = parseInt(thisBtn.id.slice(-1),10) == i,
elem = document.getElementById("dotFeat" + i);
elem.style.backgroundColor = (state ? "#ffae00" : "#969696");
}
return false;
}
FIDDLE
Even better would be to not use inline JS, but proper event handlers.