I'm writing a single page website. It has 3 'slides' like About/Music/Contact. The access to these slides is created with a dropdown menu. When you click the link in menu, the current page wrapper go visibility: hidden and through animation the following becomes visible. This works quite well, but everything happens on the root page, without changing the URL, which isn't user-friendly as if you want to share the link to the page you will always be redirected to the root.
So the question is: "How to make the url change without reloading the page (maybe through hash or smth) on the click?". Thanks in advance.
P.S. No code needed, just give me your way to make this, and I will add it into the code.
Just check with this and take it if you want this
function isElementInViewport (el) {
//special bonus for those using jQuery
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
);
}
// click-to-scroll behavior
$("a").click(function (e) {
e.preventDefault();
var section = this.href;
var sectionClean = section.substring(section.indexOf("#"));
$("html, body").animate({
scrollTop: $(sectionClean).offset().top
}, 1000, function () {
window.location.hash = sectionClean;
});
});
// listen for the scroll event
$(document).on("scroll", function() {
//console.log("onscroll event fired...");
// check if the anchor elements are visible
$(".common").each(function (idx, el) {
if ( isElementInViewport(el) ) {
// update the URL hash
if (window.history.pushState) {
var urlHash = "#" + $(el).attr("id");
window.history.pushState(null, null, urlHash);
}
}
});
});
body {
float: left;
width: 100%;
padding-bottom: 0px;
}
.common {
width: 100%;
float: left;
height: 100vh;
display: table;
}
.allbody {
float: left;
width: 100%;
}
a {
display: inline-block;
padding: 15px;
}
header {
float: left;
width: 100%;
position: fixed;
top: 0;
left: 0;
background: #fff;
}
.common h2 {
font-size: 30px;
color: #fff;
text-align: center;
padding-top: 10%;
display: table-cell;
vertical-align: middle;
}
#firstDestination {
background: #000;
}
#secondDestination {
background: #999;
}
#thirdDestination {
background: #ccc;
}
#fourthDestination {
background: #c1c1c1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<header>
first page
<a href="#secondDestination" >second page</a>
third page
fourth page
</header>
<div class="allbody">
<div class="common" id="firstDestination" ><h2>First Page</h2></div>
<div class="common" id="secondDestination"><h2>Second Page</h2></div>
<div class="common" id="thirdDestination" ><h2>Third Page</h2></div>
<div class="common" id="fourthDestination" ><h2>Fourth Page</h2></div>
</div>
Related
I'm currently working on a project where the logo color should change depending on the background color. I would prefer to do this with CSS classes.
Unfortunately, the problem is: As soon as the first DIV of the class "bg02" is scrolled, the class changes for the logo too, but as soon as the following "bg02" divs are scrolled, nothing happens anymore. What am I doing wrong? Can you help me?
Here's my code:
JavaScript
$(document).on("scroll", function() {
var scrollPos = $(document).scrollTop();
$('#logo').each(function() {
var currDiv = $(this);
var refElement = $('.bg02');
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$('#logo').removeClass("inverted");
currDiv.addClass("inverted");
} else {
currDiv.removeClass("inverted");
}
});
});
CSS
#logo {
position: fixed;
top: 20px;
left: 5%;
z-index: 100;
font-size: 26px;
font-weight: 700;
color: #000;
}
#logo.inverted {
color: #fff;
}
.bg01, .bg02 {
position: relative;
width: 100%;
height: 600px;
}
.bg01 {
background: #fff;
}
.bg02 {
background: #000;
}
HTML
<div id="logo">Logo</div>
<div class="bg01"></div>
<div class="bg02"></div>
<div class="bg01"></div>
<div class="bg02"></div>
<div class="bg01"></div>
<div class="bg02"></div>
Your issue is that $('.bg02').position() can only return a single position, so it returns the position for the first one.
To use your method of checking scrollTop(), you need to loop .bg02 not #logo
Couple of small changes to your code:
Loop .bg02:
$('.bg02').each(function() {
var refElement = $(this);
and a "break" inside the if - return false to stop the loop continuing and removing the inverted class for the later .bg02 that doesn't match
if (positioncheck) {
$("#logo").addClass("inverted");
return false;
}
You can also "tweak" when the logo gets inverted by considering its position, eg:
var scrollPos = $(document).scrollTop()
+ $("#logo").position().top
+ ($("#logo").height() / 2)
as it was, it would only invert when bg02 got to the top.
Updated snippet:
$(document).on("scroll", function() {
var scrollPos = $(document).scrollTop()
+ $("#logo").position().top
+ ($("#logo").height() / 2)
$('.bg02').each(function() {
var refElement = $(this);
if (refElement.position().top <= scrollPos
&& refElement.position().top + refElement.height() > scrollPos) {
$('#logo').addClass("inverted");
// found one, so exit .each
return false;
} else {
$('#logo').removeClass("inverted");
}
});
});
#logo {
position: fixed;
top: 20px;
left: 5%;
z-index: 100;
font-size: 26px;
font-weight: 700;
color: #000;
}
#logo.inverted {
color: #fff;
}
.bg01, .bg02 {
position: relative;
width: 100%;
height: 200px;
}
.bg01 {
background: #fff;
}
.bg02 {
background: #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="logo">Logo</div>
<div class="bg01"></div>
<div class="bg02"></div>
<div class="bg01"></div>
<div class="bg02"></div>
<div class="bg01"></div>
<div class="bg02"></div>
I have a header with a logo. This logo should appear only if the site has been scrolled.
I tried this in javascript:
if(document.getElementById("div").scrollTop != 0){
document.write("<img src='logo.jpg'>");
}
But this did not work.
How to achieve it?
Use window.addEventListener('scroll', callback) and then set the value "block" to the img's property.
window.addEventListener('scroll', function(e) {
if (document.getElementsByTagName("html")[0].scrollTop > 5) {
document.getElementsByClassName('imgHeader')[0].style.display = "block";
} else {
document.getElementsByClassName('imgHeader')[0].style.display = "none";
}
});
.imgHeader {
height: 100px;
width: 100px;
display: none;
}
div {
height: 1000px;
}
header {
position: fixed;
top: 0;
width: 100%;
}
<header><img class="imgHeader" src="https://material.angular.io/assets/img/examples/shiba1.jpg" /></header>
<div></div>
Try this one
$(document).on("scroll", function() {
if ($(document).scrollTop() > 5) {
$(".below-top-header").addClass("show-class");
} else {
$(".below-top-header").removeClass("show-class");
}
});
.content {
height: 500px;
}
.show-class {
position: fixed;
display: block !important;
}
.hide-class {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="content">
<div class="below-top-header hide-class">
Image
</div>
</div>
Unfortunately, I think you must use some JavaScript to make it work like you want.
Here is an easy snippet to show the principle I used:
Start with the logo already in the html, but with display: none in its CSS,
Use window.addEventListener('scroll', callback) to change display: none to display: block when the page is scrolled down (i.e. document.documentElement.scrollTop > 0).
var logo = document.getElementById('logo');
window.addEventListener('scroll', function(e) {
if (document.documentElement.scrollTop > 0) {
logo.style.display = 'block';
}else logo.style.display = 'none';
});
#logo {
display: none;
position: fixed;
top: 0;
background: #aaa;
}
#page {
background: #ddd;
height: 2000px;
}
<div id='logo'><img src='http://placekitten.com/200/50'></div>
<div id='page'>Start of page<br>Try to scroll down</div>
Hope it helps.
You need to add an scrollListener to the window in order to execute code when the user scrolls.
Your code only gets executed on page load.
Informations on Eventlisteners: https://developer.mozilla.org/de/docs/Web/API/EventTarget/addEventListener
window.addEventListener('scroll', function(e) {
//do something as soon as the window was scrolled
});
Be aware that the event will be triggered each time the user scrolls.
So basically I'd like to remove the class from header after the user scrolls down a little and add another class to it. Trying to figure out the simplest way of doing this but I can't make it work here is the code
$(function() {
var $sectionBox = $(".J_section-box"),
$navbarBox = $(".J_nav-bar-con"),
navHeight = $(".J_nav-bar-con").height();
$(window).on("scroll", function() {
$(window).scrollTop() >= $sectionBox.offset().top - navHeight ? $navbarBox.addClass("J_fixNavbar") : $navbarBox.removeClass("J_fixNavbar")
});
});
For simplicity, I would suggest to use toggleClass, what makes it a bit easier and better to maintain.
$(function() {
var $navbarBox = $(".J_nav-bar-con");
var navHeight = $(".J_nav-bar-con").height();
$(window).on("scroll", function() {
$navbarBox.toggleClass("J_fixNavbar", $(this).scrollTop() >= $(".J_section-box").offset().top - navHeight);
});
});
.content {
width: 100%;
height: 10000px;
}
.J_nav-bar-con {
width: 100%;
height: 100px;
background: gray;
position: fixed;
top: 0;
}
.J_section-box {
width: 100%;
height: 100px;
margin-top: 200px;
}
.J_fixNavbar {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="J_nav-bar-con"></div>
<div class="J_section-box"></div>
<div class="content"></div>
I'm trying to edit a shopify theme and the last part I'm stuck on is getting these navigation menus to open on hovering instead of clicking. The css I have for the menus is:
.site-nav {
position: relative;
padding: 0;
text-align: center;
margin: 25px 0;
a {
padding: 3px 10px;
}
li {
display: inline-block;
}
}
.site-nav--centered {
padding-bottom: $gutter-site-mobile;
}
/*================ Site Nav Links ================*/
.site-nav__link {
display: block;
white-space: nowrap;
.site-nav--centered & {
padding-top: 0;
}
.icon-chevron-down {
width: 8px;
height: 8px;
margin-left: 2px;
.site-nav--active-dropdown & {
transform: rotateZ(-180deg);
}
}
&.site-nav--active-dropdown {
border: 1px solid $color-border;
border-bottom: 1px solid transparent;
z-index: 2;
}
}
/*================ Dropdowns ================*/
.site-nav--has-dropdown {
position: relative;
}
.site-nav--has-centered-dropdown {
position: static;
}
.site-nav__dropdown {
display: none;
position: absolute;
left: 0;
padding: $dropdown-padding;
margin: 0;
z-index: $z-index-dropdown;
text-align: left;
border: 1px solid $color-border;
background: $color-bg;
left: -1px;
top: 41px;
.site-nav__link {
padding: 4px 30px 4px 0;
}
.site-nav--active-dropdown & {
display: block;
}
li {
display: block;
}
}
// Centered dropdown
.site-nav__dropdown--centered {
width: 100%;
border: 0;
background: none;
padding: 0;
text-align: center;
}
The HTML and Liquid for the header is:
{% if section.settings.align_logo == 'left' %}
<nav class="grid__item medium-up--one-half small--hide" id="AccessibleNav" role="navigation">
{% include 'site-nav' %}
</nav>
{% endif %}
And the relevant menu Javascript:
/* ================ MODULES ================ */
window.theme = window.theme || {};
theme.Header = (function() {
var selectors = {
body: 'body',
navigation: '#AccessibleNav',
siteNavHasDropdown: '.site-nav--has-dropdown',
siteNavChildLinks: '.site-nav__child-link',
siteNavActiveDropdown: '.site-nav--active-dropdown',
siteNavLinkMain: '.site-nav__link--main',
siteNavChildLink: '.site-nav__link--last'
};
var config = {
activeClass: 'site-nav--active-dropdown',
childLinkClass: 'site-nav__child-link'
};
var cache = {};
function init() {
cacheSelectors();
cache.$parents.on('click.siteNav', function(evt) {
var $el = $(this);
if (!$el.hasClass(config.activeClass)) {
// force stop the click from happening
evt.preventDefault();
evt.stopImmediatePropagation();
}
showDropdown($el);
});
// check when we're leaving a dropdown and close the active dropdown
$(selectors.siteNavChildLink).on('focusout.siteNav', function() {
setTimeout(function() {
if ($(document.activeElement).hasClass(config.childLinkClass) || !cache.$activeDropdown.length) {
return;
}
hideDropdown(cache.$activeDropdown);
});
});
// close dropdowns when on top level nav
cache.$topLevel.on('focus.siteNav', function() {
if (cache.$activeDropdown.length) {
hideDropdown(cache.$activeDropdown);
}
});
cache.$subMenuLinks.on('click.siteNav', function(evt) {
// Prevent click on body from firing instead of link
evt.stopImmediatePropagation();
});
}
function cacheSelectors() {
cache = {
$nav: $(selectors.navigation),
$topLevel: $(selectors.siteNavLinkMain),
$parents: $(selectors.navigation).find(selectors.siteNavHasDropdown),
$subMenuLinks: $(selectors.siteNavChildLinks),
$activeDropdown: $(selectors.siteNavActiveDropdown)
};
}
function showDropdown($el) {
$el.addClass(config.activeClass);
// close open dropdowns
if (cache.$activeDropdown.length) {
hideDropdown(cache.$activeDropdown);
}
cache.$activeDropdown = $el;
// set expanded on open dropdown
$el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'true');
setTimeout(function() {
$(window).on('keyup.siteNav', function(evt) {
if (evt.keyCode === 27) {
hideDropdown($el);
}
});
$(selectors.body).on('click.siteNav', function() {
hideDropdown($el);
});
}, 250);
}
function hideDropdown($el) {
// remove aria on open dropdown
$el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'false');
$el.removeClass(config.activeClass);
// reset active dropdown
cache.$activeDropdown = $(selectors.siteNavActiveDropdown);
$(selectors.body).off('click.siteNav');
$(window).off('keyup.siteNav');
}
function unload() {
$(window).off('.siteNav');
cache.$parents.off('.siteNav');
cache.$subMenuLinks.off('.siteNav');
cache.$topLevel.off('.siteNav');
$(selectors.siteNavChildLink).off('.siteNav');
$(selectors.body).off('.siteNav');
}
return {
init: init,
unload: unload
};
})();
Any help would be greatly appreciated. I feel so silly asking a simple question like this. I just can't figure out where to put :hover in the code. It seems pretty strait forward but I can't get it. You can see the site here: AlexandIvy.myShopify.com and the password to view it is staysk. I'm just talking about the top navigation menus.
This is the code from the console:
<nav class="grid__item medium-up--one-half small--hide" id="AccessibleNav" role="navigation">
<ul class="site-nav list--inline " id="SiteNav">
<li class="site-nav--has-dropdown">
<a href="/collections/bedding" class="site-nav__link site-nav__link--main" aria-has-popup="true" aria-expanded="false" aria-controls="SiteNavLabel-bedding">
Bedding
<svg aria-hidden="true" focusable="false" role="presentation" class="icon icon--wide icon-chevron-down" viewBox="0 0 498.98 284.49"><defs><style>.cls-1{fill:#231f20}</style></defs><path class="cls-1" d="M80.93 271.76A35 35 0 0 1 140.68 247l189.74 189.75L520.16 247a35 35 0 1 1 49.5 49.5L355.17 511a35 35 0 0 1-49.5 0L91.18 296.5a34.89 34.89 0 0 1-10.25-24.74z" transform="translate(-80.93 -236.76)"></path></svg>
<span class="visually-hidden">expand</span>
</a>
<div class="site-nav__dropdown" id="SiteNavLabel-bedding">
<ul>
<li>
Sheet Sets
</li>
</ul>
</div>
</li>
Since you're using JS to hide/show the dropdowns, I suggest you do this if you're comfortable with JQuery.
$('.site-nav--has-dropdown').hover(function() {
if ($(this).hasClass('activated')){
$(this).removeClass('activated');
$(this).children('.site-nav__dropdown').css('display', 'none');
}
else{
$(this).addClass('activated');
$(this).children('.site-nav__dropdown').css('display', 'block');
}
});
The idea behind this is that the child closest to .site-nav--has-dropdown which has a class name .site-nav__dropdown can be activated on hover. You can use pol's code too which provides a different (and shorter) approach.
You should use mouseover/mouseout methods in jquery.
$('.site-nav--has-dropdown').mouseover(function() {
$(this).children('.site-nav__dropdown').show();
});
$('.site-nav--has-dropdown').mouseout(function() {
$(this).children('.site-nav__dropdown').hide();
});
Or just use css :hover,
to better support touch devices you should add :focus too.
.site-nav--has-dropdown:hover .site-nav__dropdown,
.site-nav--has-dropdown:focus .site-nav__dropdown {
display: block;
}
jsfiddle demo: sfiddle.net/8p33qh9h
I've got the following code for a sticky header, but I can't get the scroll to work and it's not a smooth transition. The #top-nav-wrapper barely scrolls when the fixed header below is activated:
<script>
$(document).scroll( function() {
var value = $(this).scrollTop();
if ( value > 48 ) {
$(".header").css("position", "fixed");
$("body").css("padding-top", "90px");
} else {
$(".header").css("position", "relative");
$("body").css("padding-top", "0");
}
});
</script>
The 48 value is the height of the #top-nav-wrapper, plus it has a box-shadow.
The .header class with the search bar is what should remain.
The basic html:
<div class="headerWrapper">
<div id="top-nav-wrapper"></div>
<div class="header"></div>
</div>
The CSS:
body {
background: #EEE;
}
#top-nav-wrapper {
width: 100%;
position: relative;
box-shadow: 0px 1px 1px 0px #B8B8B8;
z-index: 2001;
background: #EEE;
}
.header {
position: relative;
left: 0;
top: 0;
width: 100%;
min-height: 90px;
z-index: 2000;
background: #EEE;
height: 90px;
box-shadow: 0px 1px 2px #C4C4C4;
}
* I tried the following suggestion, but it's the same effect as before:
<script>
$(window).scroll( function() {
var value = $(this).scrollTop();
var $body = $('body');
var docked = $body.hasClass('docked');
if ( value > 48 ) {
if( !docked ) {
$body.addClass('docked');
}
} else {
if( docked ) {
$body.removeClass('docked');
}
}
});
</script>
Any ideas appreciated.
Update - I've changed the script to the following and placed it in the head - this resolves the top nav not scrolling dynamically and I added a placeholder div after the header and before the content with the same size height as the fixed header to keep the content where it should be (because the fixed header changes the natural flow), but there's still the lag/jump when the fixed header kicks in.
Placeholder CSS:
.headerPlaceholder {
height: 90px;
width: 100%;
display: none;
}
Solution to top nav not scrolling all the way after 48px scroll height was set:
<script>
$(document).ready(function () {
var div = $('.header');
var div2 = $('.headerPlaceholder');
var start = $(div).offset().top;
$.event.add(window, "scroll", function () {
var p = $(window).scrollTop();
$(div).css('position', ((p) > start) ? 'fixed' : 'static');
$(div).css('top', ((p) > start) ? '0px' : '');
$(div2).css('display', ((p) > start) ? 'block' : 'none');
});
});
</script>
To make it a smooth transition, there might need to be a slight delay and fadein/out effect, if anyone could help with that?
You can try
$(window).scroll( function() {
var value = $(this).scrollTop();
var $body = $('body');
var docked = $body.hasClass('docked');
if ( value > 48 ) {
if( !docked ) {
$body.addClass('docked');
}
} else {
if( docked ) {
$body.removeClass('docked');
}
}
});
CSS
.docked {
padding-top: 90px;
}
.docked .header {
position: fixed;
z-index: 2005;
}
You can be more efficient if there is an overall container you can target instead of body.