I am trying to show how much a user has scrolled through the page a with progress bar and I have done it. But I have a little confusion here.
Here is the code that I found to calculate scrollPercent which works well
windowHeight = Math.max(
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
body.scrollHeight,
body.offsetHeight
);
const scrolledPercent =
((html.scrollTop || body.scrollTop) / (windowHeight - html.clientHeight)) *
100;
Initially, I thought, to get the scrollPercent , I need to get the current scrollPosition and divide that number with the total height of the page and multiply by 100% . which is like normally how we get % of something.
const scrolledPercent =
((html.scrollTop || body.scrollTop) / windowHeight) * 100;
but this line doesnot worked as I expected . If I do this the progress bar wont reach 100% even if I scroll to end of the page. I don't understand why am I wrong here !
So, my question is why do we need to decrease the html.clientHeight from windowHeight ?
Thank you.
Demo here:
// --------------------------------------------
// variables
// --------------------------------------------
const html = document.documentElement,
body = document.body,
countryList = document.querySelector(".country__list");
scrollNavigated = document.querySelector(".scroll__navigated");
let windowHeight;
// --------------------------------------------
// function
// --------------------------------------------
async function prepareListOfCountries() {
let list = await fetch("https://restcountries.eu/rest/v2/all");
list = Array.from(await list.json());
let markup = list
.map((country, index) => {
return `<li class="country__item card">
<span class="country__name">${country.name}</span
><span class="country__capital">${country.capital}</span>
<a href="javascript:;" class="country__flag">
<img src= '${country.flag}'> </a>
</li>`;
})
.slice(0, 30)
.join(" ");
countryList.innerHTML = markup;
}
function updateScrolledStatus(e) {
windowHeight = Math.max(
html.clientHeight,
html.scrollHeight,
html.offsetHeight,
body.scrollHeight,
body.offsetHeight
);
const scrolledPercent =
((html.scrollTop || body.scrollTop) / (windowHeight - html.clientHeight)) *
100;
// const scrolledPercent =
// ((html.scrollTop || body.scrollTop) / windowHeight) * 100; // this line doesnot work
scrollNavigated.style.width = scrolledPercent + "%";
}
prepareListOfCountries();
// --------------------------------------------
// event-handler
// --------------------------------------------
window.addEventListener("scroll", updateScrolledStatus);
*::after,
*::before,
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
.container {
max-width: 980px;
margin: 0 auto;
}
.justify-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.items-center {
display: flex;
align-items: center;
}
.card {
background-color: #fff;
box-shadow: 0 0 12px 12px rgba(0, 0, 0, 0.054);
border-radius: 4px;
padding: 16px;
}
.country__flag img {
height: 100%;
width: 100%;
}
.header {
padding: 24px 0;
background-color: #333;
color: #f1f1f1;
position: -webkit-sticky;
position: sticky;
}
.content {
padding: 50px 0;
}
.content__form {
margin: 0 auto;
margin-bottom: 32px;
}
.content__search {
width: 50%;
padding: 12px 16px;
border-radius: 20px;
border: 1px solid #ddd;
transition: 0.2s;
}
.content__search:hover {
box-shadow: 0 1px 6px 0 rgba(32, 33, 36, 0.28);
}
.content__search:focus {
outline: none;
}
.country__list {
margin-top: 50px;
margin: 10px auto;
}
.country__item {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
}
.country__name, .country__capital, .country__flag {
width: 33.33%;
}
.country__flag {
width: 32px;
height: 24px;
}
.scroll__navigator {
height: 2px;
margin: 0 auto 32px;
background-color: #333;
position: -webkit-sticky;
position: sticky;
top: 0;
}
.scroll__navigated {
display: block;
height: 100%;
width: 0;
background: orangered;
transition: 0.3s linear;
}
<body>
<header class="header">
<div class="container">
All countries list
</div>
</header>
<main class="content">
<div class="container">
<form class="content__form">
<input class="content__search" />
</form>
<div class="scroll__navigator">
<span class="scroll__navigated"></span>
</div>
<section class="country">
<ul class="country__list">
<li class="country__item card">
<span class="country__name">Nepal</span
><span class="country__capital">Kathmandu</span>
</li>
</ul>
</section>
</div>
</main>
</body>
As an example, say that the height of your client is 100px and the height of your whole page is 500px.
When the scroll position is 0px, you're able to see the first 100px of your site, so from 0px to 100px.
At scroll position 100px, you can see the range 100px to 200px, because you've moved the page, and therefore the visible range, on by 100px.
At scroll position 400px, you can therefore see the range 400px to 500px – in other words, you've scrolled to the bottom.
This demonstrates that the scrollable height of the page (400px) is less than the actual height of the page (500px), namely by the height of the client.
To get the percentage scrolled, you need to use the scrollable height, so it is necessary to subtract the height of the client from the height of the page to get a correct value, or you'll never be able to scroll to the bottom. It's not possible to scroll by 500px on a site that is only 500px long!
Related
So I want to have a specific scrolling section on my website, where while you scroll the wheel down (or drag the scrollbar down), the content scrolls sideways while the scrollbar on the right of the screen also moves down.
I googles like a ton yesterday for examples or packages, but didn´t find what I need - actually kind of. The best thing I found was this codepen:
https://codepen.io/alvarotrigo/pen/VwWMjVp
<div class="vertical-section">
Content above
</div>
<div class="sticky-container">
<main>
<section>
<h1>Beep</h1>
</section>
<section>
<h1>Boop</h1>
</section>
<section>
<h1>Boooom</h1>
</section>
<section>
<h1>The End</h1>
</section>
</main>
</div>
<div class="vertical-section">
Content Below
</div>
<div class="sticky-container">
<main>
<section>
<h1>Beep</h1>
</section>
<section>
<h1>Boop</h1>
</section>
<section>
<h1>Boooom</h1>
</section>
<section>
<h1>The End</h1>
</section>
</main>
</div>
-----------------------
<a href="https://alvarotrigo.com/blog/scroll-horizontally-with-mouse-wheel-vanilla-java/" target="_blank" class="read-article">
Read the article 👉
</a>
html,
body {
margin: 0;
font-family: sans-serif;
}
.vertical-section{
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
main {
overflow-x: hidden;
display: flex;
position: sticky;
top:0;
}
h1 {
margin: 0;
padding: 0;
}
section {
min-width: 50vw;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-size: 4ch;
}
section:nth-child(even) {
background-color: teal;
color: white;
}
.read-article{
position: absolute;
top: 10px;
left: 10px;
z-index: 999;
color: #000;
background: white;
padding: 10px 20px;
border-radius: 10px;
font-family: arial;
text-decoration: none;
box-shadow: rgb(50 50 93 / 25%) 0 0 100px -20px, rgb(0 0 0 / 30%) 0 0 60px -15px;
}
.read-article:hover{
background: #d5d5d5;
box-shadow: rgb(50 50 93 / 25%) 0 0 100px -20px, rgb(0 0 0 / 30%) 0 0 60px 0px;
}
iframe[sandbox] .read-article{
display: none;
}
/**
* By Alvaro Trigo
* Follow me on Twitter: https://twitter.com/imac2
*/
(function(){
init();
var g_containerInViewport;
function init(){
setStickyContainersSize();
bindEvents();
}
function bindEvents(){
window.addEventListener("wheel", wheelHandler);
}
function setStickyContainersSize(){
document.querySelectorAll('.sticky-container').forEach(function(container){
const stikyContainerHeight = container.querySelector('main').scrollWidth;
container.setAttribute('style', 'height: ' + stikyContainerHeight + 'px');
});
}
function isElementInViewport (el) {
const rect = el.getBoundingClientRect();
return rect.top <= 0 && rect.bottom > document.documentElement.clientHeight;
}
function wheelHandler(evt){
const containerInViewPort = Array.from(document.querySelectorAll('.sticky-container')).filter(function(container){
return isElementInViewport(container);
})[0];
if(!containerInViewPort){
return;
}
var isPlaceHolderBelowTop = containerInViewPort.offsetTop < document.documentElement.scrollTop;
var isPlaceHolderBelowBottom = containerInViewPort.offsetTop + containerInViewPort.offsetHeight > document.documentElement.scrollTop;
let g_canScrollHorizontally = isPlaceHolderBelowTop && isPlaceHolderBelowBottom;
if(g_canScrollHorizontally){
containerInViewPort.querySelector('main').scrollLeft += evt.deltaY;
}
}
})();
This is pretty much exactly what I was looking for but I wonder:
If there isn´t a more intuitive package that does exactly this?
If this is really that "clean"
but mostly the problem here is, because there is an eventlistener on the window only for the mousewheel, which means when not scrolling down via the mouse wheel, but with the scrollbar at the right, it won´t work.
Do you have any more ideas, what you could use or is this actually as good as it can get?
Thanks!
I am trying to replicate the same behavior as in this
codepen in IE 11 (does not have css sticky)
I am able to detect when the item is offscreen at the start with:
if (
$(".main-content").height() + $(".main-content").offset().top <
$(".main-footer").offset().top
)
but then after it reaches the end of the scroll (in this case the page), I did not manage to check when it goes offscreen again. It is probably something simple as subtracting the scroll to figure out if the element is offscreen, I am just stuck...
Here is a codepen where I stuck am now.
IE doesn't support <main> so you can't use this tag in IE 11. You can monitor the scroll bar changes through JavaScript, and then change its class according to the position of the element.
Here is the code you can refer to:
$(document).scroll(function() {
var scroH = $(document).scrollTop();
var viewH = $(window).height();
var contentH = $(document).height();
$('.main-footer').addClass('main-footer1')
if (scroH > 100) {}
if (contentH - (scroH + viewH) <= 100) { // The height from the bottom is less than 100px
}
if (contentH <= (scroH + viewH + 100)) {
$('.main-footer').removeClass('main-footer1')
$('.main-footer').addClass('main-footer2')
} else {
$('.main-footer').addClass('main-footer1')
$('.main-footer').removeClass('main-footer2')
}
});
body {
color: #fff;
font-family: arial;
font-weight: bold;
font-size: 40px;
}
.main-container {
max-width: 600px;
margin: 0 auto;
border: solid 10px green;
padding: 10px;
margin-top: 40px;
}
.main-container * {
padding: 10px;
background: #aaa;
border: dashed 5px #000;
}
.main-container *+* {
margin-top: 20px;
}
.main-header {
height: 50px;
background: #aaa;
}
.main-content {
min-height: 1000px;
}
.main-footer {
border-color: red;
}
.main-footer1 {
position: fixed;
bottom: 0px;
margin: 0 auto;
width: 570px;
}
.main-footer2 {
position: relative;
margin-top: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="main-container">
<header class="main-header">HEADER</header>
<div class="main-content">MAIN contentH</div>
<footer class="main-footer">footer</footer>
</div>
Result in IE 11:
I have a website I am working on (here is a basic example), yesterday I got some help to implement active states on the radio-style button navigation, and I am now trying to link this up so that it also changes on page scroll/when in view as currently it's only onClick.
I roughly know how to achieve this as I've done something similar before, but then it occurred to me that because the page and scrollbar are rotated to accommodate the horizontal effect, I don't know if it would now be scrollTop or scrollLeft. I've never used scrollLeft before so I am unsure how to use it correctly. I am wondering if anyone has implemented something similar before and what the correct way would be? I've tried both and nothing seems to be working. This is what I'm roughly trying to achieve (but only one class active at a time).
I thought maybe using Waypoints could be another option, but again it's hard to find anything online which explains how this works when a site is rotated multiple times.
My JS knowledge is shaky (still learning!), I'm only trying to implement what I think would work so this is probably not even correct, any help understanding what I'm doing wrong would be appreciated!
Heres the latest thing I've tried.
// --- change span classes on click
const setIconState = (icon, state) => icon.className = state
? icon.className.replace('button-off', 'button-on')
: icon.className.replace('button-on', 'button-off')
const toggleIcon = element => {
const className = element.className;
element.className = className.indexOf('button-on') > -1
? setIconState(element, false)
: setIconState(element, true);
}
const setIconActiveState = (icon, state) => icon.className = state
? icon.className = `${icon.className} active`
: icon.className = icon.className.replace('active', '')
document.querySelectorAll('.bottomnav span.icon')
.forEach(icon => {
icon.onclick = (e) => {
const {
target: clickedSpan
} = e;
const siblings = [...clickedSpan.parentElement.parentElement.querySelectorAll('span.icon')]
.filter(sibling => sibling != clickedSpan);
siblings.forEach(icon => {
setIconState(icon, false);
setIconActiveState(icon, false);
});
setIconState(clickedSpan, true);
setIconActiveState(clickedSpan, true);
};
});
// --- change span classes on scroll test
function onScroll(event){
var scrollPos = $(document).scrollTop();
$('.bottomnav a').each(function () {
var currLink = $(this);
var refElement = $(currLink.attr("href"));
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$('.bottomnav a span').removeClass("active");
currLink.addClass("active");
}
else{
currLink.removeClass("active");
}
});
}
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
color: #000;
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 100;
font-size: 7px;
text-rendering: optimizeLegibility;
overflow-x: hidden;
scroll-behavior: smooth;
}
.bottomnav {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
overflow: hidden;
position: fixed;
bottom: 0px;
width: 100%;
z-index: 2;
}
.bottomnav span {
float: left;
display: block;
color: #888;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 26px;
}
.bottomnav span:hover {
color: #fac123;
}
.bottomnav span.active {
color: #fac123;
}
#container {
overflow-y: scroll;
overflow-x: hidden;
transform: rotate(270deg) translateX(-100vh);
transform-origin: top left;
position: absolute;
width: 100vh;
height: 100vw;
white-space: nowrap;
scroll-snap-type: y mandatory;
}
#container .card {
width: 100vw;
height: 100vh;
display: inline-flex;
position: relative;
scroll-snap-align: start;
}
#player {
transform: rotate(90deg) translateY(-100vh);
transform-origin: top left;
font-size: 0;
width: 100vh;
height: 100vh;
display: flex;
/* position: absolute;*/
}
#player section > object {
width: 100vw;
overflow-x: hidden;
}
section object > div {
white-space: normal;
}
.container::-webkit-scrollbar {
display: none;
}
section {
padding: 5%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
transition: .5s ease;
}
.cardwhite {
color: white;
background-color: black;
}
.cardblack {
color: black;
background-color: white;
}
h2 {
font-size: 40px;
font-weight: 700;
font-family: 'IBM Plex Serif', sans-serif;
}
p {
font-size: 10px;
margin-bottom: 15px;
font-weight: 100;
font-family: 'IBM Plex Sans', sans-serif;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<div class="bottomnav" id="bottomnav">
<span class="icon ion-ios-radio-button-on active"></span>
<span class="icon ion-ios-radio-button-off"></span>
<span class="icon ion-ios-radio-button-off"></span>
</div>
<div class="container" id="container">
<div id="player">
<section class="card cardwhite" id="1">
<object>
<h2>Section 1</h2>
<p>Description</p>
</object>
</section>
<section class="card cardblack" id="2">
<object>
<h2>Section 2</h2>
<p>Description</p>
</object>
</section>
<section class="card cardwhite" id="3">
<object>
<h2>Section 3</h2>
<p>Description</p>
</object>
</section>
</div>
</div>
For horizontal scrolling I would go the following route, simplifying your HTML and whaty you listen for. Since touch devices can easily just swipe to scroll, all you need to do is make it accessible for people with scroll wheels. You could also add an animation, but it makes this snippet too long.
const main = document.querySelector( 'main' );
const nav = document.querySelector( 'nav' );
let scrollend;
function onwheel(){
/* When using the scrollwheel, translate Y direction scrolls to X direction. This way scrollwheel users get the benefit of scrolling down to go right, while touch and other users get default behaviour. */
event.preventDefault();
event.stopImmediatePropagation();
main.scrollLeft += event.wheelDeltaY;
}
function onscroll(){
/* When scrolling, find the nearest element to the center of the screen. Then find the link in the nav that links to it and activate it while deactivating all others. */
const current = Array.from( main.children ).find(child => {
return child.offsetLeft >= main.scrollLeft - innerWidth / 2;
});
const link = Array.from( nav.children ).reduce((find, child) => {
child.classList.remove( 'selected' );
return find || (child.href.indexOf( current.id ) >= 0 ? child : find);
}, false);
if( link ) link.classList.add( 'selected' );
clearTimeout( scrollend );
scrollend = setTimeout( onscrollend, 100 );
}
function onscrollend(){
/* After scrolling ends, snap the appropriate element. This could be done with an animation. */
clearTimeout( scrollend );
const current = Array.from( main.children ).find(child => {
return child.offsetLeft >= main.scrollLeft - innerWidth / 2;
});
main.scrollLeft = current.offsetLeft;
}
/* Bind and initial call */
main.addEventListener( 'wheel', onwheel );
main.addEventListener( 'scroll', onscroll );
onscroll();
html,
body,
main {
height: 100%;
}
body {
padding: 0;
margin: 0;
}
main {
display: flex;
overflow: auto;
width: 100%;
height: 100%;
scroll-snap-type: x proximity;
}
main section {
width: 100%;
height: 100%;
flex: 0 0 100%;
}
nav {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
nav a {
width: 1em;
height: 1em;
margin: 1em;
display: block;
overflow: hidden;
color: transparent;
border: 1px solid black;
border-radius: 50%;
}
nav a.selected {
background: black;
}
.bland { background: gray; }
.dark { background: darkgray; color: white; }
.bright { background: yellow; }
<nav>
Section 1
Section 2
Section 3
</nav>
<main>
<section class="bright" id="section-1">
<h2>Section 1</h2>
</section>
<section class="dark" id="section-2">
<h2>Section 2</h2>
</section>
<section class="bland" id="section-3">
<h2>Section 3</h2>
</section>
</main>
As mentioned, I would also prefer a design that does not flip the X and Y axis.
Doing so might bite us in the future, when we try to include non-trivial content on our pages.
Also if we don't do that axis flip, we have no need at all to do positional calculations.
So both the HTML structure and the CSS will be simpler.
AFAIK, it's not possible to do the scrolling purely in non-hacky CSS.
/**
* Change icon state on click.
*/
const icons = Array.from( document.querySelectorAll( '.icon' ));
const toggleIcon = icon => {
icon.classList.toggle( 'ion-ios-radio-button-on' );
icon.classList.toggle( 'ion-ios-radio-button-off' );
};
const clickIcon = event => {
// toggle previous active state
toggleIcon( document.querySelector( 'i.ion-ios-radio-button-on' ));
// toggle own state
toggleIcon( event.target );
};
icons.forEach( icon => icon.addEventListener( 'click', clickIcon ));
/**
* Scroll horizontally on scroll wheel.
* The combination of "scroll-behavior: smooth;" and the "<a href=#>" anchor links,
* can be reused to do and endless snapping cycle on wheel event.
*/
let scroll_state = 0;
window.addEventListener( 'wheel', event => {
window.requestAnimationFrame(() => {
// cast to -1 or +1
const offset = event.deltaY / Math.abs( event.deltaY );
scroll_state += offset;
// Arrays are zero-based.
// So if the length matches our state, restart over from the first page.
if ( scroll_state === icons.length ) scroll_state = 0;
else if ( scroll_state < 0 ) scroll_state = icons.length - 1;
// scrolll_state will now always contain the next icon to click.
icons[ scroll_state ].click();
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
overflow-x: scroll;
width: 100%;
}
main {
display: block;
height: 90vh;
width: 300vw;
}
nav {
background-color: orange;
display: block;
height: 10vh;
position: fixed;
width: 100%;
}
a {
text-decoration: none;
}
.page {
display: inline-block;
float: left;
height: 100%;
padding: 50px;
width: 100vw;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<body>
<main>
<section class="page" id="myapp_first">
<h1>First</h1>
<p>Lorem Ipsum</p>
</section>
<section class="page" id="myapp_second">
<h1>Second</h1>
<p>Lorem Ipsum</p>
</section>
<section class="page" id="myapp_third">
<h1>Third</h1>
<p>Lorem Ipsum</p>
</section>
</main>
<nav id="myapp_navigation">
<a href="#myapp_first">
<i class="icon ion-ios-radio-button-on active"></i>
</a>
<a href="#myapp_second">
<i class="icon ion-ios-radio-button-off"></i>
</a>
<a href="#myapp_third">
<i class="icon ion-ios-radio-button-off"></i>
</a>
</nav>
</body>
By leveraging the click event of the icons, we get the icons changing class and the transition for free. Adding more pages now just becomes adding the correct HTML and updating the width of the <main> element.
A last thing I would personally add, is a debounce function around the wheel event, so we don't try to scroll faster than we can render.
Without debouncing, we might want to merge the functions so we can include the class changing inside the animationFrame for hopefully less yanky visuals, but that would complicate the click events again, so i'd prefer debouncing the wheel handler.
/**
* Change icon state on click.
*/
const icons = Array.from( document.querySelectorAll( '.icon' ));
const toggleIcon = icon => {
icon.classList.toggle( 'ion-ios-radio-button-on' );
icon.classList.toggle( 'ion-ios-radio-button-off' );
icon.classList.toggle( 'active' );
};
const clickIcon = event => {
// toggle previous active state
toggleIcon( document.querySelector( '.ion-ios-radio-button-on' ));// toggle own state
toggleIcon( event.target );
};
icons.forEach( icon => icon.addEventListener( 'click', clickIcon ));
/**
* Scroll horizontally on scroll wheel.
* The combination of "scroll-behavior: smooth;" and the "<a href=#>" anchor links,
* can be reused to do and endless snapping cycle on wheel event.
*/
let scroll_state = 0;
window.addEventListener( 'wheel', event => {
// ANimation frame to smooth out the transition.
window.requestAnimationFrame(() => {
// cast to -1 or +1
const offset = event.deltaY / Math.abs( event.deltaY );
scroll_state += offset;
// Arrays are zero-based.
// So if the length matches our state, restart over from the first page.
if ( scroll_state === icons.length ) scroll_state = 0;
else if ( scroll_state < 0 ) scroll_state = icons.length - 1;
// scrolll_state will now always contain the next icon to click.
icons[ scroll_state ].click();
});
});
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
color: #000;
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 100; /* EDIT: font-weight: 100 basically equals no font weight at all */
font-size: 7px; /* EDIT: Why so small ? */
text-rendering: optimizeLegibility;
overflow-x: scroll;
overflow-y: hidden;
scroll-behavior: smooth;
height: 100vh; /* EDIT: add height, so we can scale off this */
width: 100vw; /* EDIT: add width, so we can scale off this */
}
.bottomnav {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
/*
overflow: hidden;
*/
position: fixed;
/*bottom: 0px; EDIT: not needed after we place the nav at the bottom */
height: 15vh; /* EDIT: .bottomnav height + #container height = 100vh */
width: 100%;
z-index: 2;
background-color: black;
}
.bottomnav span {
/*float: left; /* why float when flex lets us position exactly? */
display: block;
color: #888;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 26px;
}
.bottomnav span:hover {
color: #fac123;
}
.bottomnav span.active {
color: #fac123;
}
#container {
/*
overflow-y: scroll;
overflow-x: hidden;
transform: rotate(270deg) translateX(-100vh);
transform-origin: top left;
position: absolute;
*/
width: 300vw; /* EDIT: 300vw, 100 per page of 100vw */
height: 85vh; /* EDIT: .bottomnav height + #container height = 100vh */
/*scroll-snap-type: y mandatory; EDIT: only needed if we use snappoints */
}
/* EDIT: .card and section are the same elements, merged rule "container" here */
.card {
width: 100vw; /* EDIT: 100vw for each page of 100vw width */
height: 100%; /* EDIT: 100% so it scales with the container, not the screen */
display: inline-block; /* EDIT: block level, since we do not need to flex these */
float: left; /* EDIT: float left so our pages leave no space between them so 300vw = 100+100+100 . THis can be done with flexbox or grid as well, but is more complicated than needed */
/*position: relative; EDIT: not needed */
/* scroll-snap-align: start; EDIT: only needed if we use snappoints */
padding: 50px;
/* EDIT:
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
*/
/* transition: .5s ease; EDIT: I would think that "scroll-behavior: smooth;" already does this */
}
/* EDIT: Since there's no use for the extra wrapper element, positioning it absolute + flex only harms us instead of helping
#player {
transform: rotate(90deg) translateY(-100vh);
transform-origin: top left;
font-size: 0;
width: 100vh;
height: 100vh;
display: flex;
position: absolute;
}
#player section > object {
width: 100vw;
overflow-x: hidden;
}
*/
/* EDIT: I don't see any <div>s inside the objects
section object > div {
white-space: normal;
}
*/
/* EDIT: ? Attempt to remove vertical scroll? Not needed
.container::-webkit-scrollbar {
display: none;
}
*/
.cardwhite {
color: white;
background-color: black;
}
.cardblack {
color: black;
background-color: white;
}
h2 {
font-size: 40px;
font-weight: 700;
font-family: 'IBM Plex Serif', sans-serif;
}
p {
font-size: 10px;
margin-bottom: 15px;
font-weight: 100;
font-family: 'IBM Plex Sans', sans-serif;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<div id="container">
<!-- the extra player <div> is useless since the cards fully overlap it.
so it can be removed -->
<section class="card cardwhite" id="1">
<object>
<h2>Section 1</h2>
<p>Description</p>
</object>
</section>
<section class="card cardblack" id="2">
<object>
<h2>Section 2</h2>
<p>Description</p>
</object>
</section>
<section class="card cardwhite" id="3">
<object>
<h2>Section 3</h2>
<p>Description</p>
</object>
</section>
</div>
<!-- EDIT: Put the nav at the bottom so we do not have position issues -->
<div class="bottomnav" id="bottomnav">
<a href="#1">
<span class="icon ion-ios-radio-button-on active"></span>
</a>
<a href="#2">
<span class="icon ion-ios-radio-button-off"></span>
</a>
<a href="#3">
<span class="icon ion-ios-radio-button-off"></span>
</a>
</div>
I have this amateur function that changes the box-shadow on a group of elements based on the window.scrollTop position and element offset. When the user scrolls up or down from the photos, the box-shadow will move down or up respectively. So it gives the illusion of perspective. When these photo elements are in the center of the screen, the box-shadow is centered.
Right now it's not ideal and by that I mean it isn't very symmetrical and doesn't feel right. I know it because my "golden-ratio" (Not the actual Phy golden ratio, i just used this as a variable name) is just a random number. I know there is a way to use my variables to set this up properly I just can't come up with it. Looking for golden ratio (box-shadow vertical offset) to be from about -20 to 20, 0 when centered, seems to look okay.
So my question is can anyone optimize my algorithm so that the box-shadow changes as described above but more realistic?
This should only happen inside a certain vertical scroll window so
that this function isn't running while the photos are off the screen (already pretty much implemented)
The box-shadow change is subtle so there is not a HUGE shadow above or below it.
In my real code I have another function that changes the photos to be the same height as the width on window resize. Here I just have a fixed height of 160px.
Here is my code (modified to give a barebones example). Feel free to redo my function from scratch if that is cleaner.
var halfHeight;
var eleHalfHeight;
var scrollTop;
var photosOffset;
var profOffset
var distPhotos;
var goldenRatio;
$(window).scroll(function() {
halfHeight = $(window).height() / 2;
eleHalfHeight = $('.photo').height() / 2;
scrollTop = $(window).scrollTop();
photosOffset = $('.photos').offset().top - halfHeight + eleHalfHeight;
distPhotos = (photosOffset - scrollTop);
if (distPhotos < photosOffset && distPhotos > -photosOffset) {
goldenRatio = distPhotos / 25;
$('.photo-frame').css('box-shadow', '0px ' + (goldenRatio + 6) + 'px 4px 2px #c9c9c9');
};
});
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.wrapper {
display: flex;
align-items: center;
min-height: 500vh;
background-color: #fff;
}
.photos {
display: flex;
width: 100%;
justify-content: space-around;
margin-bottom: 60px;
background-color: #fff;
}
.photos .photo-frame {
width: 25%;
height: 160px;
border: 20px solid whitesmoke;
box-shadow: 0px 0px 4px 2px gray;
background-color: #000;
}
.photos .photo-frame .photo {
width: 100%;
background-color: #000;
z-index: 1;
position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<section class="photos">
<div class="photo-frame">
<div class="photo"></div>
</div>
<div class="photo-frame">
<div class="photo"></div>
</div>
<div class="photo-frame">
<div class="photo"></div>
</div>
</section>
</div>
Is it perfect™?
Nope.
Do I like it better than yours?
A little bit.
Will it help you?
I hope so, since I think it's easier to tweak — at least this was my intention. You tell me.
let wH = $(window).height(),
sO = 210, // shadowOffset (px); 50 => from -25 to +25
tO = 4, // topOffset (px) 0 => vertical symmetry,
lO = 4, // leftOffset (px) 0 => horizontal symmetry
moveshadow = function() {
let diff = $(window).scrollTop() - $(this).offset().top,
tOH = $(this)[0].offsetHeight,
should = (-diff < wH) && (diff < tOH),
factor = should ?
-((diff / wH + 1) / (1 + tOH / wH) - .5) * sO + tO :
0;
if (should) {
$(this).css({
"box-shadow": lO +"px " +
factor +
"px 8px -2px rgba(0,0,0,.1), " + lO + "px " +
(factor + sO / 20) +
"px 17px 4px rgba(0,0,0,.07), " + lO + "px " +
(factor - sO / 20) +
"px 22px 8px rgba(0,0,0,.06), " + lO/2 + "px " +
factor / 20 +
"px 21px 1px rgba(0,0,0,.12)"
})
}
};
$(window)
.on('resize', function() {
wH = $(window).height()
})
.on('scroll resize', function() {
$('.photo-frame').each(moveshadow)
})
body {
background-color: #fff;
text-align: center;
}
.wrapper {
padding: 120vh 30px;
}
.photos {
display: flex;
align-items: center;
justify-content: space-between;
}
.photo {
width: 20vw;
height: 32.36vw;
background-color: #eee;
}
.photo-frame {
padding: 20px;
border: 1px solid #eee;
box-sizing: border-box;
}
#media(min-width: 1000px) {
.wrapper {
width: 900px;
display: inline-block;
}
.photo {
width: 200px;
height: 323.6px;
background-color: #eee;
}
}
#media(min-width: 641px) {
.photo-frame:first-child { transform: translateY(-10vw) }
.photo-frame:last-child { transform: translateY(10vw) }
}
#media(max-width: 640px) {
.photos {
flex-direction: column;
}
.photo {
width: 60vw;
height: 37.08vw;
background-color: #eee;
}
.photo-frame {
margin: 7.5vw 0;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<section class="photos">
<div class="photo-frame">
<div class="photo"></div>
</div>
<div class="photo-frame">
<div class="photo"></div>
</div>
<div class="photo-frame">
<div class="photo"></div>
</div>
</section>
</div>
playground
:: }<(((*> ::)
I'm using an hero section to show some content.
It's responsive using the padding-bottom percentage technique and an inner absolute positioned container to center the content.
Now the catch: reaching a breakpoint, let's say 768px, and on lower window size I would like the box to start growing again.
I found some js/jQuery code around the web and was able to get the result but it only works if I load the page when the window is <768px. In that case it works brilliantly. But if the page is loaded in a larger window the below 768px resizing get lost.
This is the html:
<div class="row row-home-hero" id="hero">
<div class="cont">
<h1>Title</h1>
<h2>Subtitle</h2>
<div class="cta-hero-home">
» CTA1
<span class="cta-hero-spacer">or</span>
» CTA2
</div>
</div>
</div>
This is the JS.
It's a mess since it's a mix from different sources.
And I'm using Wordpress so I've to replace some $ with jQuery.
Please forgive me :)
function screenClass() {
if(jQuery(window).innerWidth() < 768) {
jQuery('.row-home-hero').addClass('small-hero');
} else {
jQuery('.row-home-hero').removeClass('small-hero');
jQuery('.row-home-hero').css("height", "");
}
}
// Fire.
screenClass();
// And recheck if window gets resized.
jQuery(window).bind('resize',function(){
screenClass();
});
if (document.documentElement.clientWidth < 768) {
var $li = jQuery('.small-hero'), // Cache your element
startW = $li.width(); // Store a variable reference
function setMarginDiff() {
area = 500000;
width = jQuery('.small-hero').width();
jQuery('.small-hero').height(Math.ceil(area/width/1.7));
}
setMarginDiff(); // Do on DOM ready
jQuery(window).resize(setMarginDiff); // and on resize
}
And this is the CSS
.row-home-hero {
background-position: center;
-webkit-background-size: cover;
background-size: cover;
background-repeat: no-repeat;
position: relative;
background-color: red;
}
.row-home-hero:before {
display: block;
content: "";
width: 100%;
padding-top: 46%;
}
.row-home-hero .cont {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40%;
text-align: center;
}
a.cta-hero-link {
display: block;
width: 100px;
max-width: 80%;
line-height: 40px;
background: white;
color: #1b9fdd;
text-transform: uppercase;
text-decoration: none;
margin: 10px auto;
text-align: center;
font-weight: 500;
box-shadow: 0px 0px 7px 1px rgba(0,0,0,.4);
}
#media screen and (max-width: 768px) {
.row-pre-footer .cont div {
width: 100%;
padding: 0 5%;
float: none;
margin: 0 auto 30px;
}
.progetto-footer, .loghi-footer {
width: 100%;
max-width: 320px;
margin: 0 auto 30px;
float: none;
}
.double-box .tib-tab {
float: none;
width: 90%;
margin: 5% auto;
padding-bottom: 90%;
}
.tib-box h2, .tab-box h2 {
font-size: calc(28px + (46 - 28) * (100vw - 320px) / (768 - 320));
margin-bottom: 18vw;
}
.double-box-inner p {
font-size: 22px;
line-height: 30px;
}
.row-home-hero.small-hero {
height: 500px;
}
.row-home-hero:before {
display: block;
content: "";
width: 100%;
padding-top: 0;
}
}
And this is a working demo
Thanks!
I moved the if (document.documentElement.clientWidth < 768) { block inside the resize event. So that it gets called whenever the window is resized.
In the original version, it would only get called when the page was loaded (and only if the screen was smaller than 768). With this adjustment, it will always be rechecked when resized.
I also merged all your code into one smaller function.
var breakpoint = 768
var area = 500000
var target = $('.row-home-hero')
$(window).bind('resize', function() {
if(window.innerWidth < breakpoint) {
var width = target.width()
target.addClass('small-hero')
target.height(Math.ceil(area / width / 1.7))
} else {
target.removeClass('small-hero')
target.css('height', '')
}
})
.trigger('resize')