I have side dot navigation on website - standard position: fixed dot nav. Now i have also 2 types of sections with 2 types of background on same website - lets assume one are white and another black. My problem is that when dots are not visible when navigation is hovering black section. So i tried to write script that detects if section have certain class and if certain dot is on this section - if yes then color of dot is changed. I had certain success but after i finish i realise that my script works only in one way ( scrolling top to bottom ) and even if i will detect bottom to top scrolling it will not work properly when i change direction in the middle of website. I arleady spend on this quite a while and im clueless - here is code that i have so far - its working when you scroll from top to bottom.
Do you have any other suggestion or perhaps some library to solve this issue ?
EDIT: Layout is quite artistic - so there are some boxes that floats from left or right dynamicly and dots also have to change when box is there, its why i splice my array and push #myname to it.
EDIT2: You can see how it works under this link ( not optimized, slow load time http://lektor.ionstudio.pl/)
var sections = [];
$("section[id]").each(function() {
sections.push("#" + this.id);
})
sections.splice(0,0,"#myname");
i = 0;
$(window).scroll(function(){
var content = $("section.current").hasClass("white-section");
$("#banner .navigation li").each(function(){
var thisElem = this.getBoundingClientRect();
section = sections[i];
section = document.querySelector(section);
sectionRect = section.getBoundingClientRect();
if(sectionRect.top - thisElem.top <= 0) {
if($(section).hasClass("white")) {
$(this).addClass("black");
} else {
$(this).removeClass("black");
}
if(sectionRect.top + $(section).outerHeight(true) <= thisElem.top ) {
i++;
}
}
})
})
As mentioned in the comments i would use elementFromPoint to check if a dot it over a section with a specific class.
Try this:
(function(win, doc) {
var dotsSelector = '.dot';
var sectionsSelector = 'section';
var classes = ['white', 'black'];
var dots = [].slice.call(doc.querySelectorAll(dotsSelector));
var dotPositions = dots.map(function(dot) {
var rect = dot.getBoundingClientRect();
return rect.top + rect.height / 2 ;
});
var sections = [].slice.call(doc.querySelectorAll(sectionsSelector));
win.addEventListener('scroll', function(event) {
for(var i = 0; i < dots.length; ++i) {
var element = doc.elementFromPoint(0, dotPositions[i]);
var section = null;
while(!section && element) {
section = sections.find(function(s) { return s === element });
element = element.parentNode;
}
if(section) {
dots[i].classList.toggle(classes[0], section.classList.contains(classes[1]));
}
}
});
})(window, document);
section {
min-height: 400px;
background: white;
}
section.black {
background: black;
}
.menu {
position: fixed;
top: 50%;
transform: translateY(-50%);
left: 10px;
z-index: 20;
}
.dot {
border-radius: 50%;
width: 20px;
height: 20px;
background: black;
margin-bottom: 10px;
}
.dot.white {
background: white;
}
* {
padding: 0;
margin: 0;
}
ul {
list-style: none;
}
<section></section>
<section class="black"></section>
<section></section>
<section class="black"></section>
<section></section>
<section class="black"></section>
<ul class="menu">
<li class="dot"></li>
<li class="dot"></li>
<li class="dot"></li>
<li class="dot"></li>
<li class="dot"></li>
<li class="dot"></li>
</ul>
It only works on one way scroll because of the way you are looping. You need to remove the dependence of the 'i' increment in your function.
The only reason you are using this increment is because you need to be able to uniquely identify the section.
Are you able to add some meta data to "this" element that you could use to identify the section instead? Something like
<li data-sectionID="999" ....>Menu Node</li>
...
section = $(this).data("sectionID");
Related
How can we make a horizontal row of link elements (with variable width/text length) with overflow hidden (or without, depending on how this is usually done..) function so that the last element is positioned behind the first and so on in each left or right direction, to create a circular scroll?
I have this so far:
const horizontalContainer = document.querySelector('.horizontal-container')
const horizontalLinks = document.querySelectorAll('.horizontal-link')
let touchStart = 0
let touchX = 0
let isDragging = false
const handleTouchStart = (e) => {
touchStart = e.clientX || e.touches[0].clientX
isDragging = true
}
const handleTouchMove = (e) => {
if (!isDragging) return
touchX = e.clientX || e.touches[0].clientX
touchStart = touchX
horizontalLinks.forEach(element => {
element.style.transform = "translate(" + (touchStart) + "px," + "0px)";
})
}
const handleTouchEnd = () => {
isDragging = false
}
horizontalContainer.addEventListener('mousedown', handleTouchStart)
horizontalContainer.addEventListener('mousemove', handleTouchMove)
horizontalContainer.addEventListener('mouseleave', handleTouchEnd)
horizontalContainer.addEventListener('mouseup', handleTouchEnd)
horizontalContainer.addEventListener('selectstart', () => { return false })
.horizontal-container {
display: flex;
overflow-x: hidden;
width: 100%;
}
.horizontal-container::-webkit-scrollbar {
display: none;
}
.horizontal-link {
padding-left: 20px;
padding-right: 20px;
right: 0;
}
<div class="horizontal-container">
<div class="horizontal-link">
ONE
</div>
<div class="horizontal-link">
TWO
</div>
<div class="horizontal-link">
THREE
</div>
<div class="horizontal-link">
FOUR
</div>
<div class="horizontal-link">
FIVE
</div>
</div>
Edit: Unless you have the time to show me an example, I'm more than happy with just an explanation for how this can be done calculating translate: transform(x,y) to reposition the links when the left or right position of a link div of variable width reaches the right or left position of the screen depending on the screen width, which can also be variable, so that what the exact amount of overflow that peeks outside the viewport on the right will peek out the same amount on the left side of the viewport.
Edit2: Even though I know little about programming or the Javascript language (yet) I do know that this is not a "carousel" which is much easier to implement, that I already have created on my own so I know every detail of it. And a scrollbar is also programmed to move between a left end or right end position - this cannot be used here without a lot of ugly hacks so a new scrolling function needs to be implemented from scratch. I also know that jQuery will not help me to understand or learn more, and that this is nothing one would use - ever - whether you are an amateur or not.
What you are requesting is a carousel pattern. You can configure a carousel to show multiple slides at once. In this case each "slide" would be a menu item.
I have mocked up an example using https://kenwheeler.github.io/slick/
The only downfall is that it snaps to each slide, which may or may not be what you want. If it is not what you want then you would be best looking at other slider alternatives.
But the main point is that what you are requesting is normally done with a slider/carousel pattern. You just need to look at it differently, and you are not limited to show one "slide" at a time.
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel#1.8.1/slick/slick.css"/>
<div class="menu-slider" style="width: 400px;">
<div style="padding:20px;">Link 1</div>
<div style="padding:20px;">Another Link 2</div>
<div style="padding:20px;">Yet Another Link 3</div>
<div style="padding:20px;">Menu Link 4</div>
<div style="padding:20px;">Link 5</div>
<div style="padding:20px;">Menu Link 6</div>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/slick-carousel#1.8.1/slick/slick.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('.menu-slider').slick({
dots: false,
infinite: true,
centerMode: false,
variableWidth: true
});
});
</script>
Here's a working solution for one that will "fill" its parent in order to create the effect.
I was working on an alternative that didn't fill but it still needs more work.
$(function () {
init();
});
let base_width = 0;
function init() {
setupMenu();
}
function handleScroll(event) {
if (base_width === 0) {
// no need to do anything
return;
}
const $menu = $(event.currentTarget);
const scroll_left = $menu[0].scrollLeft;
// check backwards scroll
if (scroll_left <= base_width) {
const new_left = 2 * base_width - (base_width - scroll_left);
$menu[0].scrollLeft = new_left;
return;
}
if (scroll_left < base_width * 2) {
return;
}
// get remainder
const new_left = scroll_left % (base_width * 2);
$menu[0].scrollLeft = new_left + base_width;
}
function setupMenu() {
const $menu = $("#menu-fill");
const $parent = $menu.parent();
const menu_width = $menu.width();
const parent_width = $parent.width();
if (menu_width >= parent_width) {
// no need to duplicate
return;
}
base_width = menu_width;
// setup a base to clone
const $template = $menu.clone();
// get num times to duplicate to "fill" menu (i.e. allow scrolling)
// NOTE: we duplicate 1 "extra" so that we can scroll "backwards"
const num_duplicate = Math.ceil(parent_width / menu_width) + 2;
for (let i = 0; i < num_duplicate; i++) {
const $new_menu = $template.clone();
$menu.append($new_menu.children());
$new_menu.remove();
}
$menu[0].scrollLeft = base_width;
$menu.scroll(handleScroll)
}
* {
box-sizing: border-box;
font-family: sans-serif;
}
*::-webkit-scrollbar {
background-color: transparent;
height: 6px;
width: 6px;
border-radius: 3px;
}
*::-webkit-scrollbar-thumb {
background-color: #ccc;
border-radius: 3px;
}
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.wrap {
width: 250px;
height: 100px;
border: 2px solid #777;
margin: 0 auto 20px auto;
}
.wrap.fill {
width: 500px;
}
.menu {
display: inline-flex;
max-width: 100%;
overflow: auto;
}
.item {
padding: 5px 10px;
background-color: #426ac0;
color: #fff;
border-right: 1px solid #1d3464;
white-space: nowrap;
}
.item:last-child {
border-right: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrap fill">
<div class="menu" id="menu-fill">
<div class="item">Item 1</div>
<div class="item">Number 2</div>
</div>
</div>
I have a sticky up arrow image for my webpage as follows:
HTML:
<img src = "images/sticky-btn-light.png" id = "myBtn" alt = "sticky-up-button">
And CSS:
#myBtn {
visibility: hidden;
position: fixed;
bottom: 50px;
right: 30px;
z-index: 99;
cursor: pointer;
padding: 15px;
border-radius: 10px;
width: 5%;
opacity: 0.5 ;
}
The button disappears when the user scrolls down and appears when the user scrolls up based on this JS code.
window.onscroll = function(e) {
console.log(this.oldScroll > this.scrollY);
if((this.scrollY == 0) || (this.oldScroll < this.scrollY) ){
document.getElementById('myBtn').style.visibility = 'hidden';
}
else if(this.oldScroll > this.scrollY){
document.getElementById('myBtn').style.visibility = 'visible';
}
this.oldScroll = this.scrollY;
}
Now, I want to change the color of the button based on its changing position on the screen. As I scroll the page, the sticky button will be in different sections as below.
If the sticky button is in Section A, it should be red. And if it is in Section B, it should be blue. Please note that it's the page that is moving, not the button. The button is in a fixed position.
For this, I need the id of the section in which the sticky button is overlapping at any given moment. Is there any way to get that information through JavaScript?
PS: I have adjusted the details to make things more clear. It's the page that is moving. So, if I use Element.getBoundingClientRect() for #myBtn, will I not get the same top/y values for that element wherever I scroll on the page?
You can use element.getBoundingClientRect() to get the x,y of the top left corner and the x,y of the bottom right corner of a element.
var arrow = document.getElementById('myBtn');
var arrowRect = arrow.getBoundingClientRect();
console.log(arrowRect.top, arrowRect.right, arrowRect.bottom, arrowRect.left);
var section = document.getElementById('section1');
var sectionRect = section.getBoundingClientRect();
console.log(sectionRect.top, sectionRect.right, sectionRect.bottom, sectionRect.left);
Then you can check collisions with the arrow and the section. In your case the x-axis doesn't matter:
// This checks if the arrow is touching the section
( arrowRect.bottom > sectionRect.top && arrowRect.top < sectionRect.bottom )
// This checks if the arrow isn't touching the section, then inverts it (faster)
!( arrowRect.bottom < sectionRect.top || arrowRect.top > sectionRect.bottom )
Here, I did an example for you to test and implement.
this will also help understand getBoundingClientRect even with fixed position
var arrow= document.getElementById('myBtn');
window.onscroll = function(e) {
var arrowBound = arrow.getBoundingClientRect();
var divs = document.querySelectorAll(".container > div");
divs.forEach(function(item){
var divBound = item.getBoundingClientRect();
var color= item.getAttribute("arrowColor");
if ( arrowBound.bottom > divBound.top && arrowBound.top < divBound.bottom )
{
arrow.style.borderTopColor = color
}
})
}
#myBtn {
position: fixed;
top: 10px;
left:270px;
z-index: 99;
cursor: pointer;
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid #f00;
}
.container{
width:300px;
height:800px;
z-index: 81;
}
.container > div {
width:300px;
height:100px;
border:1px solid black;
}
<div class="container">
<a id = "myBtn" href = "#navbar-scroll"></a>
<div arrowColor="red"> box red </div>
<div arrowColor="blue"> box blue </div>
<div arrowColor="green"> box green </div>
</div>
You can get coords of a html element with getBoundingClientRect() method, call this function and you'll get the Y coords of element (pixels), then you can use an if conditional
function getCoords(elem) {
let box = elem.getBoundingClientRect();
let body = document.body;
let docEl = document.documentElement;
let scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
let clientTop = docEl.clientTop || body.clientTop || 0;
return Math.round(box.top + scrollTop - clientTop);
}
if(getCoords(elem) > 100){
elem.className = 'your_class_name'
}
.your_class_name{
color: red;
}
The thing is that I need to make a vertical images slider,so that when i press arrow down/arrow up every image changes it's position (the highest one goes bottom,the previous take it's place)
what it should look like:
what i have got so far:
$(function(){
var $vsliderboxes = $('#vsliderboxes'),
$vslidernav = $('#vslidernav'),
boxHeight = $vsliderboxes.height(),
current_index = 0;
function clickslide(){
clearInterval(intervalTimer);
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(function () {
intervalTimer = window.setInterval(autoslide, 2000);
}, 2500);
var index = $(this).index();
current_index = index;
$vsliderboxes.children().stop().animate({
top : (boxHeight * index * -1)
}, 500);
}
function autoslide(){
current_index++;
if (current_index >= $vsliderboxes.children().children().length) {
current_index = 0;
}
$vslidernav.find('a').eq(current_index).trigger('click');
}
$vslidernav.find('a').click(clickslide);
var intervalTimer = window.setInterval(autoslide, 2000),
timeoutTimer = null;
});
#vslidernav ul {
list-style: none;
padding: 0;
}
#vslidernav ul a {
padding: 0;
cursor: pointer;
height: 50px;
}
#vslidernav ul a:active {
color: #9C9A99;
}
#vslidernav ul a li {
height: 50px;
}
#vslidernav ul .active li {
}
.#vslidernav ul a:active {
background: transparent;
color: #9C9A99;
}
.vslider {
display: inline-block;
}
#vslidernav {
float: left;
width: 100px;
z-index: 1;
height: 250px;
}
#vsliderboxes {
position : relative;
overflow : hidden;
}
#vsliderboxes div {
height: 250px;
width: 900px;
}
#vsliderboxs-inner {
position : relative;
width : 900px;
height : 250px;
}
<div class="vslider">
<div id="vslidernav">
<ul>
<a id="1">
<li><img src="img/arrtop.gif"></li>
</a>
<a id="2">
<li><img src="img/arrdown.gif"></li>
</a>
<a id="3">
<li></li>
</a>
</ul>
</div>
<div id="vsliderboxes">
<div id="vsliderboxs-inner">
<div id="box1" class="active"><img src="img/slide1.gif"></div>
<div id="box2" class="inactive"><img src="img/slide2.gif"></div>
<div id="box3" class="inactive"><img src="img/slide3.gif"></div>
</div>
</div>
</div>
thanks for any advice
I think, that it isn't possible to solve this issue like you try to.
Because, when you work with the "top" property, you can't take one image from the top and append it to the other end because appending the image, will move the other images to another place --> the top property wouldn't be correct any more.
I think the contributed sliders (e.g. http://www.jssor.com/demos/vertical-slider.slider) work with the transform CSS property.
transform: translate3d()
Try to research about this property.
Roko C. Buljan answered on this page: loop carousel jquery
He uses a scrollTop loop for your problem.
I've also written a simple slider some time ago. I have now implemented the Roku C. Buljan method. Feel free to look at my code on Bitbucket.
https://bitbucket.org/d-stone/jqueryslider
An excerpt may help you:
value = prev_or_next == 'next' ? self.globals.slide_height : 0;
last = $('#container').find('> div:last');
first = $('#container').find('> div:first');
if(prev_or_next == 'prev') { // click on "next"-button
first.before(last); // put last element before first
settings.elements.inner.scrollTop(self.globals.slide_height); // set the scrollTop to 1 slide-height
}
// animation itself:
$('#container').stop().animate({scrollTop: value}, {
duration: settings.slide_speed,
done: function() {
if(prev_or_next == 'next') {
// put first item after last
last.after(first);
}
}
});
I'd advise you to validate your HTML (W3C Validator). There are some errors inside.
Invalid HTML can be the reason for some CSS and Javascript Errors.
I'm looking to learn how to do this left menu :
http://js.devexpress.com/New/15_2/#HTML_5_JS_Core
When you scroll down the page, the "active" menu item change.
p.s.
Is there a name for this type of menu?
regards,
yaniv
Scroll Navigation
That is how we call these type of navigation bars. Basically you have to listen to the scroll event and calculate which element is in the viewport at the moment than you add a class to your navigation that marks the current menu element.
There is a nice demo built in jQuery but because jQuery is a thing of the past, I built one in Vanilla JS. See comments for explanations.
There are different ways to define which is the current element. In my Example it is the last one whose top line just passed the top line of the browser.
Working demo
window.onscroll = onScroll;
function onScroll() {
var removeActiveClass = function (elements) {
for (var i = 0; i < elements.length; ++i) {
elements[i].classList.remove('active');
}
}
var anchors = document.querySelectorAll('#menu-center a');
var previousRefElement = null;
for (var i = 0; i < anchors.length; ++i) {
// Get the current element by the id from the anchor's href.
var currentRefElement = document.getElementById(anchors[i].getAttribute('href').substring(1));
var currentRefElementTop = currentRefElement.getBoundingClientRect().top;
// Searching for the element whose top haven't left the top of the browser.
if (currentRefElementTop <= 0) {
//The browser's top line haven't reached the current element, so the previous element is the one we currently look at.
previousRefElement = anchors[i];
// Edge case for last element.
if (i == anchors.length - 1) {
removeActiveClass(anchors);
anchors[i].classList.add("active");
}
} else {
removeActiveClass(anchors);
previousRefElement.classList.add("active");
break;
}
}
}
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
.menu {
width: 100%;
height: 75px;
position: fixed;
background-color:rgba(4, 180, 49, 0.6);
}
#menu-center {
width: 980px;
height: 75px;
margin: 0 auto;
}
#menu-center ul {
margin: 15px 0 0 0;
}
#menu-center ul li {
list-style: none;
margin: 0 30px 0 0;
display: inline;
}
.active {
font-size: 14px;
color: #fff;
text-decoration: none;
line-height: 50px;
}
a {
font-size: 14px;
color: black;
text-decoration: none;
line-height: 50px;
}
.content {
height: 100%;
width: 100%;
}
#portfolio {background-color: grey;}
#about {background-color: blue;}
#contact {background-color: red;}
<div class="menu">
<div id="menu-center">
<ul>
<li><a class="active" href="#home">Home</a></li>
<li>Portfolio</li>
<li>About</li>
<li>Contact</li>
</ul>
</div>
</div>
<div id="home" class="content"></div>
<div id="portfolio" class="content"></div>
<div id="about" class="content"></div>
<div id="contact" class="content"></div>
This is not exactly menu type, it is the way how you can position objects by html.
You can use position:Abosule property to achieve this effect:
http://www.w3schools.com/css/tryit.asp?filename=trycss_position_fixed
By this given divs are "flying" above the res of the page. In your case it could be a menu.
EDIT:
To sync this you need to detect when given anchor is currently seen.
It can be done by jQuery, this is sample draft of code, should explain clue of solution:
// list of header on page
var positions = [
$("#anchor1").offset().top,
$("#anchor2").offset().top,
$("#anchor3").offset().top,
];
var menu_objects= [
"#menu1",
"#menu2",
"#menu3"
];
var $w = $(window).scroll(function(){
// clear old
for(var v in menu_objects)
$(v).css({"color","white"});
for(var i=positions.length-1;i>=0;i--)
{
if(positions[i]>=$w.scrollTop())
{
$(menu_objects[i]).css({"color","red"});
break;
}
}
});
I am trying to, sort of, emulate the effect here. Essentially, during scrolling, change the css (drop shadow), and when the element comes back to original position (remove shadow).
I am able to detect scroll, but not able to figure out how to detect the return to the original un-scrolled state.
HTML
<div id="container">
<ul>
<li id="one">el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li><li>el</li>
</ul>
</div>
CSS
html, body {
margin: 0;
padding: 0;
height: 100%;
}
#container {
height: 100px;
width: 500px;
border: 1px solid #000000;
overflow: scroll;
}
JS (with jquery)
var p = $('#one');
var position0 = p.position().top;
$('#container').scroll(function () {
if (p.position().top != position0) {
console.log('p.position: ' + p.position().top);
$('#container').css('background-color', 'pink');
}
});
JSFIDDLE: http://jsfiddle.net/nrao89m3/
PS: From console.log it doesn't seem to return to its original value at all.
Just add an else block:
var p = $('#one');
var position0 = p.position().top;
$('#container').scroll(function () {
if (p.position().top != position0) {
console.log('p.position: ' + p.position().top);
$('#container').css('background-color', 'pink');
} else {
$('#container').css('background-color', 'white');
}
});
http://jsfiddle.net/vyjbwne2/