I have coded an infinite scroll. When the user scrolls it will load an additional 20 items which makes it a long list.
I want the scroll to loads new items and clear the previous items.
var listElm = document.querySelector('#infinite-list');
// Add 20 items.
var nextItem = 1;
var loadMore = function() {
for (var i = 0; i < 20; i++) {
var item = document.createElement('li');
item.innerText = 'Item ' + nextItem++;
listElm.appendChild(item);
}
}
// Detect when scrolled to bottom.
listElm.addEventListener('scroll', function() {
if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight) {
loadMore();
}
});
// Initially load some items.
loadMore();
#infinite-list {
/* We need to limit the height and show a scrollbar */
width: 200px;
height: 300px;
overflow: auto;
/* Optional, only to check that it works with margin/padding */
margin: 30px;
padding: 20px;
border: 10px solid black;
}
/* Optional eye candy below: */
li {
padding: 10px;
list-style-type: none;
}
li:hover {
background: #ccc;
}
<ul id='infinite-list'>
</ul>
If you empty the list before that, would it be ok?
var listElm = document.querySelector('#infinite-list');
// Add 20 items.
var nextItem = 1;
var loadMore = function() {
listElm.innerHTML = ''
for (var i = 0; i < 20; i++) {
var item = document.createElement('li');
item.innerText = 'Item ' + nextItem++;
listElm.appendChild(item);
}
}
// Detect when scrolled to bottom.
listElm.addEventListener('scroll', function() {
if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight ) {
loadMore();
}
});
// Initially load some items.
loadMore();
#infinite-list {
/* We need to limit the height and show a scrollbar */
width: 200px;
height: 300px;
overflow: auto;
/* Optional, only to check that it works with margin/padding */
margin: 30px;
padding: 20px;
border: 10px solid black;
}
/* Optional eye candy below: */
li {
padding: 10px;
list-style-type: none;
}
li:hover {
background: #ccc;
}
<ul id='infinite-list'>
</ul>
You can empty the list every time loadMore() is called.
var listElm = document.querySelector('#infinite-list');
// Add 20 items.
var nextItem = 1;
var loadMore = function() {
//Here we empty the list to remove the old results
listElm.innerHTML = ''
//And then load the new items
for (var i = 0; i < 20; i++) {
var item = document.createElement('li');
item.innerText = 'Item ' + nextItem++;
listElm.appendChild(item);
}
}
// Detect when scrolled to bottom.
listElm.addEventListener('scroll', function() {
if (listElm.scrollTop + listElm.clientHeight >= listElm.scrollHeight ) {
loadMore();
}
});
// Initially load some items.
loadMore();
Related
I made a multi section landing page with a navbar containing the links to the sections of the page
I need to give an active class to the link of the section closest to the top of the page, I tried using the scroll event listener but it is not working properly
//respond to scrolling by giving navbar active state for current section
``document.addEventListener("scroll", function() {
const scrollPos = document.body.scrollTop;
let pos = 0;
for (let i = 0; i < sec.length; i++) {
// determining pos of current section
pos+=sec[i].offsetHeight ;
let secPos = sec[i].getBoundingClientRect().y;
secPos - scrollPos <= 0 && secPos + pos >= scrollPos ? list[i].classList.add("active") : list[i].classList.remove("active")
}
});```
The scroll event is working properly, but your code does not correctly address the list of elements you are itering.
just try changing list[] to sec[]
document.addEventListener( "DOMContentLoaded", function() {
let sec = document.querySelectorAll('section');
console.log(sec);
sec[0].classList.add("active")
document.addEventListener("scroll", function() {
const scrollPos = document.body.scrollTop;
let pos = 0;
for (let i = 0; i < sec.length; i++) {
// determining pos of current section
pos+=sec[i].offsetHeight ;
let secPos = sec[i].getBoundingClientRect().y;
if ((secPos - scrollPos <= 0) && (secPos + pos >= scrollPos))
sec[i].classList.add("active");
else
sec[i].classList.remove("active");
}
})
})
section {
font-size: 80px;
text-align: center;
width: 300px;
margin: 20px auto;
background-color: pink;
border: 1px solid red;
}
section.active {
border: 1px solid blue;
background-color: cyan;
}
<section>1</section>
<section>2</section>
<section>3</section>
<section>4</section>
<section>5</section>
<section>6</section>
<section>7</section>
<section>8</section>
I have the following html structure:
<div id="pixel_1" class="pixelarea"></div>
<div id="pixel_2" class="pixelarea"></div>
<div id="pixel_3" class="pixelarea"></div>
each div is 10px wide and 10px high. The whole surface is 102 divs wide and 200 divs high.(1020 pixel wide and 2000pixel high)
This is my jquery function:
var getNumericPart = function(id) {
var num = id.replace(/[^\d]+/, '');
return num;
};
$('body').on('click','.pixelarea',function(e) {
e.preventDefault();
var id = getNumericPart($(this).attr('id')); //get only the number from the id
var count = parseInt($("#selectedpixelsum").text());
if($('#pixel_' +id).hasClass('selected')){
$('#pixel_' +id).removeClass('selected');
count--;
$('#selectedpixelsum').html(count);
}else{
$('#pixel_' +id).addClass('selected');
count++;
$('#selectedpixelsum').html(count);
}
});
How can I make sure that only adjacent divs can be selected?
By this I mean the divs below, above and to the right and left of the selected divs.
Because you know the number of items per row (102), it is simple to use the elements index to allow or disallow selection.
Left of centre is index - 1
Right of centre is index + 1
Above centre is index - 102
Below centre is index + 102
Assumes that only neighbours of the first pixel selected may be selected:
/*
* Create pixel grid
*/
const $pixelsContainer = $('.pixels');
for (let i = 0; i < (102 * 5); i++) {
$pixelsContainer.append('<div class="pixelarea" />');
}
/* END Create pixel grid */
let selectedIndex = -1;
$('.pixelarea').on('click', function() {
const clickedIndex = $(this).index();
if (clickedIndex === selectedIndex) {
/*
* Primary selected element was clicked. Remove all selections;
*/
$('.pixelarea.selected').removeClass('selected');
selectedIndex = -1;
return;
}
if (selectedIndex === -1) {
/*
* This is the primary selection
*/
selectedIndex = clickedIndex;
$(this).addClass('selected');
}
if (clickedIndex === selectedIndex - 1 || /* Left of primary */
clickedIndex === selectedIndex + 1 || /* Right of primary */
clickedIndex === selectedIndex - 102 || /* Above primary */
clickedIndex === selectedIndex + 102) { /* Below primary */
/*
* Select / Deselect this element
*/
$(this).toggleClass('selected');
}
});
.pixels {
display: flex;
flex-wrap: wrap;
width: 1020px;
}
.pixelarea {
box-sizing: border-box;
background: #f2f2f2;
border: 1px solid #e6e6e6;
width: 10px;
height: 10px;
}
.pixelarea:hover {
background: orange;
}
.pixelarea.selected {
background: limegreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="pixels"></div>
Assumes neighbours of the last pixel selected can be selected:
/*
* Create pixel grid
*/
const $pixelsContainer = $('.pixels');
const columnCount = 102;
const rowCount = 5;
for (let i = 0; i < (columnCount * rowCount); i++) {
$pixelsContainer.append('<div class="pixelarea" />');
}
/* END Create pixel grid */
let selectedIndex = -1;
$('.pixelarea').on('click', function() {
const clickedIndex = $(this).index();
if (selectedIndex === -1) {
/*
* This is the first selection
*/
selectedIndex = clickedIndex;
$(this).addClass('selected');
return;
}
if (clickedIndex === selectedIndex - 1 ||
clickedIndex === selectedIndex + 1 ||
clickedIndex === selectedIndex - columnCount ||
clickedIndex === selectedIndex + columnCount) {
selectedIndex = $(this).index();
$(this).addClass('selected');
}
});
.pixels {
display: flex;
flex-wrap: wrap;
width: 1020px;
}
.pixelarea {
box-sizing: border-box;
background: #f2f2f2;
border: 1px solid #e6e6e6;
width: 10px;
height: 10px;
}
.pixelarea:hover {
background: orange;
}
.pixelarea.selected {
background: limegreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="pixels"></div>
Assumes neighbours of any selected pixel can be selected:
(Fixes selectable pixels from flowing on to incorrect rows)
/*
* Create pixel grid
*/
const $pixelsContainer = $('.pixels');
const columnCount = 102;
const rowCount = 5;
for (let i = 0; i < (columnCount * rowCount); i++) {
$pixelsContainer.append('<div class="pixelarea" />');
}
/* END Create pixel grid */
let selectedIndex = -1;
$('.pixelarea').on('click', function() {
const clickedIndex = $(this).index();
if (selectedIndex === -1) {
/*
* This is the first selection
*/
selectedIndex = clickedIndex;
$(this).addClass('selected');
markAllowed();
return;
}
if ($(this).hasClass('allowed')) {
selectedIndex = $(this).index();
$(this).addClass('selected');
markAllowed();
}
});
function markAllowed() {
const row = Math.floor(selectedIndex / columnCount) + 1;
// Allow left if we haven't clicked the first element in the row
if (selectedIndex - 1 > 0) {
$('.pixelarea').eq(selectedIndex - 1).addClass('allowed');
}
/*
* Allow right if we haven't clicked the last element in the row
*/
if (((selectedIndex + 1) / row) < columnCount) {
$('.pixelarea').eq(selectedIndex + 1).addClass('allowed');
}
// Allow above if we haven't clicked in the first row
if (row > 1) {
$('.pixelarea').eq(selectedIndex - columnCount).addClass('allowed');
}
// Allow below
$('.pixelarea').eq(selectedIndex + columnCount).addClass('allowed');
}
.pixels {
display: flex;
flex-wrap: wrap;
width: 1020px;
}
.pixelarea {
box-sizing: border-box;
background: #f2f2f2;
border: 1px solid #e6e6e6;
width: 10px;
height: 10px;
}
.pixelarea:hover {
background: orange;
}
.pixelarea.selected {
background: limegreen;
}
.allowed {
background: lightGreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="pixels"></div>
I need some help or advice I am trying to limit the random number to only generate 20 out of the 50 numbers and this activated when a button is pressed/clicked sometimes i get duplicates too which i would to stop happening
function lottoNumbers()
{
var lottoNums = [];
for(var i=0; i <1 ; i++)
{
var temp = Math.floor(Math.random() *50);
if(lottoNums.indexOf(temp) == -1)
{
lottoNums.push(temp);
document.getElementById('circle'+i).innerHTML = lottoNums[i];
}
else
{
i--;
}
}
}
Rather than using a for loop - use a while loop (ie - while length < 20) and as you are doing - only push the numbers into it if they are not already there.
For demo purposes- i am building a list dynamically and then styling the li's to give a rounded shape with border-radius;
lottoNumbers();
function lottoNumbers() {
var lottoNums = [];
var lottoNumStr = '';
while(lottoNums.length <20) {
var temp = Math.floor(Math.random() *50);
if(lottoNums.indexOf(temp) == -1) {
lottoNums.push(temp);
lottoNumStr += '<li>' + temp + '</li>';
}
}
document.getElementById('lottoNumList').innerHTML = lottoNumStr;
}
#lottoNumList {
list-style: none;
}
#lottoNumList li {
border: solid 1px #d4d4d4;
border-radius: 50%;
display: inline-block;
margin: 5px;
padding: 4px;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
}
<h1>Lotto Numbers</h1>
<ul id ="lottoNumList"></ul>
I have created a set of dots using div tags inside a div tag. My need is when I drag the last dot, the whole set of dots should move and sit where mouse pointer is placed at present. I tried achieving it using addeventlistner for mouse clicks but failed in my attempt.
Can someone point out the intuition in the segment below?
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
The immediate answer to your question is that dots.style.marginLeft needs to be equal to a string, containing the units.
Hence, this would work:
dots.style.marginLeft = ((currentMousePointerPos+latestMousePointerPos) + "px");
However:
Your mouseup listener only listens to the event that the mouse click is released when it's over the element, so it doesn't do much. If you assign the listener to the whole document, the listener's function would be activated no matter where the mouseup event occurres.
currentMousePointerPos + latestMousePointerPos doesn't represent the final position of the mouse.
If we fix these two issues the will code still operate weirdly, because the left side of the dots element is set to the mouse's last position.
Therefore we just need to subtract the element's width from the marginLeft property.
The following code combines everything I've mentioned:
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
var currentMousePointerPos;
// Notice how the listener is bound to the whole document
document.addEventListener("mouseup", function(event) {
currentMousePointerPos = event.pageX;
dots.style.marginLeft = ((currentMousePointerPos-dots.offsetWidth) + "px");
})
}
})
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
Is this what you are looking for?
You should use the mousemove event instead to detect any dragging
after mousedown.
var dots = document.createElement("div");
dots.className = "dots";
document.body.appendChild(dots);
var dotarray = [];
for (index = 0; index < 10; index++) {
dotarray[index] = document.createElement("div");
dotarray[index].className = "dot";
dots.appendChild(dotarray[index]);
}
dotarray[9].addEventListener("mousedown", function(event) {
if (event.which == 1) {
window.addEventListener('mousemove', move, true);
/*var currentMousePointerPos, latestMousePointerPos;
currentMousePointerPos = event.pageX;
dotarray[9].addEventListener("mouseup", function(event) {
latestMousePointerPos = event.pageX;
if (currentMousePointerPos != latestMousePointerPos) {
dots.style.marginLeft = currentMousePointerPos + latestMousePointerPos;
}
})*/
}
});
window.addEventListener("mouseup", function(event) {
window.removeEventListener('mousemove', move, true);
});
function move(e){
var div = document.getElementsByClassName('dots')[0];
div.style.position = 'absolute';
div.style.top = -8+e.clientY + 'px';
div.style.left = -135+8+e.clientX + 'px';
}
.dot {
width: 8px;
height: 8px;
border-radius: 4px;
background-color: red;
display: inline-block;
margin-left: 5px;
}
.dots {
border: 1px solid black;
width: 135px;
}
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');
}