I have two aside divs (essentially two side bars) which must be hidden to enable a "fullscreen" of my central div main, I am using flexbox to assign proportions of the screen, however, upon redisplaying the elements as display : block, it completely ruins the original layout.
The code for hiding and displaying is below. I am using d3 for DOM manipulation.
function handlefullscreen(){
if(d3.select("#fullscreen").attr("data-selected") == 1){
d3.select("#fullscreen").attr("data-selected", 0);
d3.selectAll(".aside")
.style("display", "block")
.transition()
.ease(d3.easeCubic)
.duration("250")
.style("opacity", 1).on("end",
redraw);
} else{
previousWidth = document.getElementById('left').offsetWidth;
d3.select("#fullscreen").attr("data-selected", 1);
d3.selectAll(".aside")
.transition()
.ease(d3.easeCubic)
.duration("250")
.style("opacity", 0)
.on("end", function(){
d3.select(this).style("display", "none");
redraw();
});
}
}
Below is the CSS I am using for the flex elements:
.wrapper {
height: 100%;
margin: 0 -10px;
display: flex;
flex-flow: row wrap;
font-weight: bold;
text-align: center;
}
.wrapper > * {
padding: 0px;
}
.main {
background: #ffffff;
}
.aside-1 {
box-shadow:5px 1px 6px #4E565E;
background: #d8d8d8;
z-index: 1;
}
.aside-2 {
margin: 0 0px;
box-shadow:-5px 1px 6px #4E565E;
background: #d8d8d8;
}
#media all and (min-width: 600px) {
.aside { flex: 1 20%; }
}
#media all and (min-width: 800px) {
.main { flex: 3 60%; }
.aside-1 { order: 1; }
.main { order: 2; }
.aside-2 { order: 3; }
}
You should change the display block to display flex.
d3.selectAll(".aside")
.style("display", "flex")
.... // rest of the code
Remember that flex is another mode for the display property.
Hope this helps
Related
im new to javascript so if my code isn't the best i apologise in advance! Im trying to display the tags when a certain name is clicked with the click event (eventListener), but I have no idea how! I tried writing the code how I want it, but now im stuck. I want the tags to be displayed in the aside 1 if the name is clicked. Any help is appreciated!
window.addEventListener('load', init);
const cardsContainer = document.querySelector("#cards")
const birdNames = ["Koolmees", "Specht", "kerkuil"]
const birdImages = ["https://www.natuurpunt.be/sites/default/files/styles/content-wide/public/koolmees_fr_van_bauwel.jpg?itok=arfFjeTb&c=312068de040ea85bb4eb43164e28b3b2", "https://www.buitenleven.nl/wp-content/uploads/2019/10/grote-bonte-specht.jpg", "https://www.vogelhuisjes.nl/media/wysiwyg/vogels-in-de-tuin/vogels-in-nederland/xkerkuil.jpg.pagespeed.ic.8a2v4rM0Z3.jpg"]
const birds = [
{ name: "Koolmees", image: "https://www.natuurpunt.be/sites/default/files/styles/content-wide/public/koolmees_fr_van_bauwel.jpg?itok=arfFjeTb&c=312068de040ea85bb4eb43164e28b3b2", tag:"rotterdam, koolmees, kleine vogel" },
{ name: "specht", image: "https://www.buitenleven.nl/wp-content/uploads/2019/10/grote-bonte-specht.jpg" },
{ name: "kerkuil", image: "https://www.vogelhuisjes.nl/media/wysiwyg/vogels-in-de-tuin/vogels-in-nederland/xkerkuil.jpg.pagespeed.ic.8a2v4rM0Z3.jpg" }
]
const Birdtags = ["rotterdam, koolmees, kleine vogel", "specht, nijmegen, kleine vogel", "uil, eindhoven, grote vogel, roofvogel"]
let Field;
let target;
function init()
{
//Click handler for every card
Field = document.getElementById('field');
Field.addEventListener('click', playingFieldClickHandler);
//starting
addCard();
//Listen to input of chosen name by user
let playForm = document.getElementById('form');
playForm.addEventListener('submit', formSubmitHandler);
}
function addCard(birdImage, birdName){
const cardDiv = document.createElement("flex-item")
cardDiv.classList.add("card")
cardsContainer.appendChild(cardDiv)
const img = document.createElement("img")
img.src = birdImage
cardDiv.appendChild(img)
const nameDiv = document.createElement("div")
nameDiv.innerText = birdName
cardDiv.appendChild(nameDiv)
}
function playingFieldClickHandler(e)
{
/** what do I put here???*/
}
function formSubmitHandler(e)
{
//Prevent default form submit
e.preventDefault();
//If the value is the right one, you won!
if (birdNames === "koolmees") {
/** display tags */
} else if (birdNames === "specht") {
/** display tags */
}
else if (birdNames === "kerkuil") {
/** display tags */
}
}
function addCards(){
for(let i = 0; i<birdNames.length; i++){
addCard(birdImages[i], birdNames[i])
}
}
addCards()
flex-container {
/* We first create a flex layout context */
display: flex;
/* Then we define the flow direction
and if we allow the items to wrap
* Remember this is the same as:
* flex-direction: row;
* flex-wrap: wrap;
*/
flex-flow: row wrap;
/* Then we define how is distributed the remaining space */
justify-content: space-around;
padding: 0;
margin: 0;
list-style: none;
}
flex-item {
background: #ABEBC6;
padding: 5px;
width: 250px;
height: 200px;
margin-top: 10px;
line-height: 50px;
color: black;
font-weight: bold;
font-size: 3em;
text-align: center;
}
nav {
display: flex;
flex-flow: row wrap;
justify-content: flex-end;
list-style: none;
margin: 0;
background: #A2D9CE;
}
nav a {
text-decoration: none;
display: block;
padding: 1em;
color: white;
}
nav a:hover {
background: #1565C0;
}
wrapper {
display: flex;
flex-flow: row wrap;
font-weight: bold;
text-align: center;
}
wrapper > * {
padding: 10px;
flex: 1 100%;
}
header {
background: #DAF7A6;
}
footer {
background: #28B463;
}
main {
text-align: left;
background: #A2D9CE;
}
aside {
background: #28B463;
}
#media all and (min-width: 600px) {
.aside { flex: 1 0 0; }
}
#media all and (min-width: 800px) {
main { flex: 3 0px; }
aside { order: 1; }
main { order: 2; }
footer { order: 3; }
}
body {
width: 100%;
}
#media all and (max-width: 800px) {
nav {
justify-content: space-around;
}
}
#media all and (max-width: 600px) {
nav {
flex-flow: column wrap;
padding: 0;
}
nav a {
text-align: center;
padding: 10px;
border-top: 1px solid rgba(255, 255, 255,0.3);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
nav li:last-of-type a {
border-bottom: none;
}
}
p1 {
font-family: "Times New Roman", Times, serif;
font-size: 40px;
}
p2 {
font-family: Arial, Helvetica, sans-serif;
}
p3 {
font-family: "Lucida Console", "Courier New", monospace;
}
img {
width: 250px;
height: 150px;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/style2.css">
<title>Test week 2</title>
</head>
<body>
<wrapper>
<header><p1>Vogel magazine voor vogelspotters!</p1></header>
<main>
<flex-container id="cards">
</flex-container>
</main>
<aside>Aside 1</aside>
<footer>Footer</footer>
</wrapper>
<script src="js/DOM4.js"></script>
</body>
</html>
Admittedly I modified the HTML structure a little as there were errors with the markup, possibly screwed up your CSS and changed the multiple source arrays for a single Object literal but you should be able to adapt the following to suit your needs. I made this do what I thought you were trying to do.
window.addEventListener('load', init);
const cardsContainer = document.querySelector('#cards');
const aside = document.querySelector('#wrapper aside');
/*
If you re-structure your data into a single Object literal
you will, I think, find less issues with maintaining several
arrays and ensuring they all have the same number of items etc
This sort of data structure can easily be created in the form of
JSON data from a database query!
Each sub-object ( aka - Bird ) can be extended easily to have additional
properties quite easily, especially if database driven.
If you remove, from the img src, the `scheme` you can generally get the image to download
using the same scheme as the parent page. One day ALL websites will be hosted on SSL
so this will be redundant but in the meantime this might help prevent warnings
*/
const birds={
'Koolmees':{
src:'//www.natuurpunt.be/sites/default/files/styles/content-wide/public/koolmees_fr_van_bauwel.jpg',
tags:'rotterdam, koolmees, kleine vogel'
},
'Specht':{
src:'//www.buitenleven.nl/wp-content/uploads/2019/10/grote-bonte-specht.jpg',
tags:'specht, nijmegen, kleine vogel'
},
'kerkuil':{
src:'//www.vogelhuisjes.nl/media/wysiwyg/vogels-in-de-tuin/vogels-in-nederland/xkerkuil.jpg.pagespeed.ic.8a2v4rM0Z3.jpg',
tags:'uil, eindhoven, grote vogel, roofvogel'
}
};
/*
It is unclear from the code what you hope to do with the `submithandler`
especially given that there is no form ( or was no form ) so I guessed
that you were hoping to do something similar to what this method does..
if there is no `figcaption` below the image it will add the caption and
assign the `tags` text which is assigned to the image as a dataset attribute
*/
const clickhandler=function(e){
let fig=e.target.parentNode.querySelector('figcaption');
if( fig==null ){
fig=document.createElement('figcaption');
fig.textContent=this.dataset.tags
e.target.parentNode.appendChild( fig );
}else{
e.target.parentNode.removeChild(fig)
}
aside.textContent=fig==null ? '' : this.dataset.tags;
}
function init(){
/* the first does nothing, the 2nd has errors and the 3rd is incomplete...
//Click handler for every card
document.getElementById('field').addEventListener('click', playingFieldClickHandler);
//starting
//addCard(src,name,desc);
//Listen to input of chosen name by user
document.getElementById('form').addEventListener('submit', formSubmitHandler);
*/
document.getElementById('cards').querySelectorAll('.card').forEach( card => {
card.addEventListener('click',clickhandler );
});
}
function addCard(birdImage, birdName, birdTags){// now takes 3 arguments
let item = document.createElement('flex-item');
item.classList.add('card');
item.dataset.tags=birdTags; //assign the tags as a dataset atttribute
cardsContainer.appendChild(item)
let img = document.createElement('img')
img.src = birdImage;
img.title=birdTags; // tags also assigned for the img title
item.appendChild(img)
let name = document.createElement('div')
name.innerText = birdName
item.appendChild(name)
}
/**********************************************************
Unclear how these two functions are really to be used
- unchanged
*/
function playingFieldClickHandler(e)
{
/** what do I put here???*/
}
function formSubmitHandler(e)
{
//Prevent default form submit
e.preventDefault();
//If the value is the right one, you won!
if (birdNames === "koolmees") {
/** display tags */
} else if (birdNames === "specht") {
/** display tags */
}
else if (birdNames === "kerkuil") {
/** display tags */
}
}
/*
With the new data structure a new approach to iterating through the
data is required. Using the `object.keys` method allows us to quickly
iterate through each sub-object. The `key` is the bird name.
*/
function addCards(){
Object.keys( birds ).forEach( key => {
let bird=birds[ key ];
addCard( bird.src, key, bird.tags )
})
}
addCards()
body{
width:100%;
height:100vh;
margin:0;
padding:0;
box-sizing:border-box;
}
flex-container {
/* We first create a flex layout context */
display: flex;
/* Then we define the flow direction
* and if we allow the items to wrap
* Remember this is the same as:
* flex-direction: row;
* flex-wrap: wrap;
*/
flex-flow: row wrap;
flex:10;
margin:auto;
/* Then we define how is distributed the remaining space */
justify-content: space-around;
padding: 0;
list-style: none;
}
flex-container figcaption{
font-size:1rem;
line-height:1rem;
color:white;
}
flex-item {
background: #ABEBC6;
padding: 5px;
width: 250px;
min-height: 200px;
max-height:250px;
margin-top: 10px;
line-height: 50px;
color: black;
font-weight: bold;
font-size: 3em;
text-align: center;
cursor:pointer;
}
nav {
display: flex;
flex-flow: row wrap;
justify-content: flex-end;
list-style: none;
margin: 0;
background: #A2D9CE;
}
nav a {
text-decoration: none;
display: block;
padding: 1em;
color: white;
}
nav a:hover {
background: #1565C0;
}
#wrapper {
display: flex;
flex:10;
flex-direction:column;
font-weight: bold;
text-align: center;
min-height:100vh;
margin:auto;
padding:0;
}
#wrapper > * {
padding: 10px;
flex: 1 100%;
}
header {
background: #DAF7A6;
flex:2!important;
order:1;
display:flex;
align-items:center;
justify-content:center;
}
footer {
background: #28B463;
flex:1!important;
order:3;
display:flex;
align-items:center;
justify-content:center;
}
main {
text-align: left;
background: #A2D9CE;
flex:50!important;
order:2;
}
aside {
display:flex;
align-items:center;
justify-content:center;
background: #28B463;
flex:1;
margin:auto;
max-height:2rem;
width:100%;
padding:0!important;
}
#media all and (min-width: 600px) {
.aside { flex: 1 0 0; }
}
#media all and (min-width: 800px) {
main { flex: 3 0px; }
aside { order: 1; }
main { order: 2; }
footer { order: 3; }
}
#media all and (max-width: 800px) {
nav {
justify-content: space-around;
}
}
#media all and (max-width: 600px) {
nav {
flex-flow: column wrap;
padding: 0;
}
nav a {
text-align: center;
padding: 10px;
border-top: 1px solid rgba(255, 255, 255,0.3);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
nav li:last-of-type a {
border-bottom: none;
}
}
p1 {
font-family: "Times New Roman", Times, serif;
font-size: 40px;
}
p2 {
font-family: Arial, Helvetica, sans-serif;
}
p3 {
font-family: "Lucida Console", "Courier New", monospace;
}
img {
width: 250px;
height: 150px;
}
<div id='wrapper'><!-- There is no HTML element `wrapper` -->
<header><p1>Vogel magazine voor vogelspotters!</p1></header>
<main>
<flex-container id="cards"></flex-container><!-- unusual to assign custom elements without accompanying javascript/css -->
</main>
<aside>Aside 1</aside>
<footer>Footer</footer>
</div>
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 want to make div with size equal to 1 screen pixel regardless current browser zoom.
It's important for me that this element will be as small as possible, but will cause line break if the next element tries to get same size as container or larger.
I used 1px but when page zoom is less then 100% it gets unstable - sometimes it works, but sometimes Chrome shrinks element to 0 and does not wrap the next element. So I hoped to enlarge element on other zooms, but I don't know how to detect it.
Seems like on my computer next snippet works fine, but in the real code similar thing works unstable if browser zoom is less than 100%. In this example I want red div to take 1px of the screen width regardless current zoom. Is that possible?
~function () {
var s = "When silver div gets too wide it should jump down..."
var i = s.length
var div = document.querySelector(".content");
setInterval(function () {
div.textContent = s.slice(0, i = ++i % (s.length + 1))
}, 100)
}()
section {
width: 15em;
height: 2em;
line-height: 2em;
border: 1px solid;
}
div {
display: inline-block;
vertical-align: top;
height: 100%;
}
.narrow {
width: 1px;
background: red;
margin-bottom: 1px;
}
.content {
background: silver;
white-space: nowrap;
}
<section><div class=narrow></div><div class=content></div></section>
For webkit it's possible with media queries:
~function () {
var s = "When silver div gets too wide it should jump down..."
var i = s.length
var div = document.querySelector(".content");
setInterval(function () {
div.textContent = s.slice(0, i = ++i % (s.length + 1))
}, 100)
}()
section {
width: 15em;
height: 2em;
line-height: 2em;
border: 1px solid;
}
div {
display: inline-block;
vertical-align: top;
height: 100%;
}
.narrow {
width: 1px;
background: red;
margin-bottom: 1px;
}
#media (-webkit-min-device-pixel-ratio: .1 ) { .narrow { width: 10px; } }
#media (-webkit-min-device-pixel-ratio: .25) { .narrow { width: 4px; } }
#media (-webkit-min-device-pixel-ratio: .3 ) { .narrow { width: 3px; } }
#media (-webkit-min-device-pixel-ratio: .5 ) { .narrow { width: 2px; } }
#media (-webkit-min-device-pixel-ratio: 1 ) { .narrow { width: 1px; } }
.content {
background: silver;
white-space: nowrap;
}
<section><div class=narrow></div><div class=content></div></section>
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')