In the last few weeks Google show a carousel for Stack Overflow result, It's awesome and move smooth but weird for me, there is no JavaScript DOM changing and even CSS that cause to horizontal scroll, I cannot find it out.
Even I read about CSS Horizontal Scroll but it is so different and it is just for Google Chrome in other browsers it doesn't exist.
After some searches and experiments I found out this weird carousel is actually a long horizontal division with display: none scroll bar, but how with grab and moving mouse pointer, the division scroll moving? is that a native chrome trick? or just use JavaScript to calculate the motion of horizontal scroll?
Actually, I have found the answer to my question, but I decided to post it here now. Google website developers use many sexy tricks to implement the UI and this carousel is one of them, definitely, this is not DOM manipulation, it's a simple scrolled division. Because of this, the division moves very smoothly and even with dragging move properly.
A scroll has some native behavior in some devices like Apple devices or other touch devices, even Microsoft new laptops have some features about scrolling by touch. but if we use a few JavaScript codes to handle dragging it will be nice, see following code:
HINT: You can use your native device horizontal scrolling features like the two-finger horizontal scroll on MacBook trackpad OR using click and drag to move the carousel horizontally
var slider = document.querySelector('.items');
var isDown = false;
var startX;
var scrollLeft;
slider.addEventListener('mousedown', function (e) {
isDown = true;
slider.classList.add('active');
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
slider.addEventListener('mouseleave', function () {
isDown = false;
slider.classList.remove('active');
});
slider.addEventListener('mouseup', function () {
isDown = false;
slider.classList.remove('active');
});
slider.addEventListener('mousemove', function (e) {
if (!isDown) return;
e.preventDefault();
var x = e.pageX - slider.offsetLeft;
var walk = (x - startX) * 3; //scroll-fast
slider.scrollLeft = scrollLeft - walk;
});
#import url(https://fonts.googleapis.com/css?family=Rubik);
body,
html {
color: #fff;
text-align: center;
background: #efefef;
font-family: Helvetica, sans-serif;
margin: 0;
}
.grid-container {
background: #efefef;
font-family: 'Rubik', sans-serif;
}
#supports (display: grid) {
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas: "header header header" "title title footer" "main main main";
}
#media screen and (max-width: 500px) {
.grid-container {
grid-template-columns: 1fr;
grid-template-rows: 0.3fr 1fr auto 1fr;
grid-template-areas: "header" "title" "main" "footer";
}
}
.grid-item {
color: #fff;
background: skyblue;
padding: 3.5em 1em;
font-size: 1em;
font-weight: 700;
}
.header {
background-color: #092a37;
grid-area: header;
padding: 1em;
}
.title {
color: #555;
background-color: #f4fbfd;
grid-area: title;
}
.main {
color: #959595;
background-color: white;
grid-area: main;
padding: 0;
overflow-x: scroll;
overflow-y: hidden;
}
.footer {
background-color: #5bbce4;
grid-area: footer;
padding: 0.6em;
}
.items {
position: relative;
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
transition: all 0.2s;
transform: scale(0.98);
will-change: transform;
user-select: none;
cursor: pointer;
}
.items.active {
background: rgba(255, 255, 255, 0.3);
cursor: grabbing;
cursor: -webkit-grabbing;
transform: scale(1);
}
.item {
display: inline-block;
background: skyblue;
min-height: 100px;
min-width: 400px;
margin: 2em 1em;
}
#media screen and (max-width: 500px) {
.item {
min-height: 100px;
min-width: 200px;
}
}
}
a {
display: block;
color: #c9e9f6;
text-decoration: underline;
margin: 1em auto;
}
a:hover {
cursor: pointer;
}
p {
text-align: left;
text-indent: 20px;
font-weight: 100;
}
i {
color: skyblue;
}
<div class="grid-container">
<main class="grid-item main">
<div class="items">
<div class="item item1"></div>
<div class="item item2"></div>
<div class="item item3"></div>
<div class="item item4"></div>
<div class="item item5"></div>
<div class="item item6"></div>
<div class="item item7"></div>
<div class="item item8"></div>
<div class="item item9"></div>
<div class="item item10"></div>
</div>
</main>
</div>
For ReactJs developer I created a good hook for supporting horizontal scroll in desktop:
import { useEffect } from 'react';
import type { MutableRefObject } from 'react';
const useHorizontalScroll = (
scrollWrapperRef: MutableRefObject<HTMLElement>,
scrollSpeed = 1
): void => {
useEffect(() => {
const horizWrapper = scrollWrapperRef.current;
let isDown = false;
let startX: number;
let scrollLeft: number;
horizWrapper?.addEventListener('mousedown', (e: any) => {
isDown = true;
startX = e.pageX - horizWrapper?.offsetLeft;
scrollLeft = horizWrapper?.scrollLeft;
});
horizWrapper?.addEventListener('mouseleave', () => {
isDown = false;
});
horizWrapper?.addEventListener('mouseup', () => {
isDown = false;
});
horizWrapper?.addEventListener('mousemove', (e: any) => {
if (!isDown) return;
e.preventDefault();
const x = e.pageX - horizWrapper?.offsetLeft;
const walk = (x - startX) * scrollSpeed;
horizWrapper.scrollLeft = scrollLeft - walk;
});
}, [scrollSpeed, scrollWrapperRef]);
};
export default useHorizontalScroll;
Related
I have this navbar and everytime I click an option in the navbar the absolute positioned indicator gets the position of the option on the left and the width with the help of getBoundingClientRect() and it is moved to the target.
The problem is when I resize the window the indicator changes it's position and moves away.To stay in the same place when I resize the window I applied an eventListener to the window and everytime is resized I get the new values of left and width with getBoundingClientRect().
It works but I wonder if that is a bad way to do it because of the calculations that happen everytime the window is resized and if that is the case what is a better way to do this.
Here is the code:
const navigator = document.querySelector('.navigator');
const firstOption = document.querySelector('.first-option');
const navOptions = document.querySelectorAll('.nav-option');
const nav = document.querySelector('nav');
navigator.style.left = `${firstOption.getBoundingClientRect().left}px`;
navigator.style.width = `${firstOption.getBoundingClientRect().width}px`;
nav.addEventListener('click', function(e) {
if(e.target.classList.contains('nav-option')) {
navOptions.forEach(option => option.classList.remove('nav-option-active'));
e.target.classList.add('nav-option-active');
navigator.style.left = `${e.target.getBoundingClientRect().left}px`;
navigator.style.width = `${e.target.getBoundingClientRect().width}px`;
};
});
window.addEventListener('resize', function() {
let navOptionActive = nav.querySelector('.nav-option-active');
navigator.style.left = `${navOptionActive.getBoundingClientRect().left}px`;
navigator.style.width = `${navOptionActive.getBoundingClientRect().width}px`;
});
* {
margin: 0;
padding: 0;
}
nav {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
margin: 100px auto;
padding: 7vh 30vw;
width: auto;
background:#eeeeee;
}
.nav-option {
padding: 0 15px;
font-size: 22px;
cursor: pointer;
}
.navigator {
position: absolute;
left: 0;
bottom: 0;
height: 5px;
background: orangered;
transition: .4s ease all;
}
#media (max-width: 1200px) {
.nav-option {
font-size: 18px;
padding: 10px;
}
}
<nav>
<div class="navigator"></div>
<div class="nav-option first-option nav-option-active">HOME</div>
<div class="nav-option">INFO</div>
<div class="nav-option">CONTACT</div>
<div class="nav-option">ABOUT</div>
<div class="nav-option">MENU</div>
</nav>
You can make your <nav> element tightly wrap the buttons, then position the underline relative to the <nav>. A new wrapper <div> around the <nav> takes care of the margins and gray background. Instead of getBoundingClientRect() you then need to use offsetLeft and offsetWidth.
Note that this doesn't handle the changes in response to your #media query. For that, you could add a resize listener that specifically only handles changes across the 1200px threshold. Alternatively, you could reparent the underline to be a child of the actual nav button while it's not animating. Neither solution is great, but both would get the job done.
const navigator = document.querySelector('.navigator');
const firstOption = document.querySelector('.first-option');
const navOptions = document.querySelectorAll('.nav-option');
const nav = document.querySelector('nav');
navigator.style.left = `${firstOption.offsetLeft}px`;
navigator.style.width = `${firstOption.offsetWidth}px`;
nav.addEventListener('click', function(e) {
if(e.target.classList.contains('nav-option')) {
navOptions.forEach(option => option.classList.remove('nav-option-active'));
e.target.classList.add('nav-option-active');
navigator.style.left = `${e.target.offsetLeft}px`;
navigator.style.width = `${e.target.offsetWidth}px`;
};
});
* {
margin: 0;
padding: 0;
}
.nav-wrapper {
margin: 100px 0;
display: flex;
justify-content: center;
background: #eeeeee;
}
nav {
position: relative;
display: flex;
}
.nav-option {
padding: 7vh 15px;
font-size: 22px;
cursor: pointer;
}
.navigator {
position: absolute;
left: 0;
bottom: 0;
height: 5px;
background: orangered;
transition: .4s ease all;
}
#media (max-width: 1200px) {
.nav-option {
font-size: 18px;
padding: 10px;
}
}
<div class="nav-wrapper">
<nav>
<div class="navigator"></div>
<div class="nav-option first-option nav-option-active">HOME</div>
<div class="nav-option">INFO</div>
<div class="nav-option">CONTACT</div>
<div class="nav-option">ABOUT</div>
<div class="nav-option">MENU</div>
</nav>
</div>
If you have to use getBoundingClientRect (which honestly has nothing wrong with it), you can throttle the call, so that only the last resize after sufficient time has passed will execute. There are zillion ways of doing this, I will leave one example:
window.onresize = (function(id = null, delay = 600, oEvent = null){
return function fire(event){
return (new Promise(function(res,rej){
if (id !== null){
oEvent = event;
rej("busy");
return;
}
id = setTimeout(function(){
res(oEvent || event);
},delay);
})).then(function(event){
id = null;
console.log(event, "do getBoundingClientRect call");
}).catch(function(){void(0);});
};
}());
Replace console.log with what you want to do.
Your other option is to switch to intersection observer, if you can restructure your rendering logic. That will require some work
I am working on a WordPress site and I have a snippet of html that iterates with repeating classes.
I am attempting to create a click function but only affect the element that is clicked. All in JavaScript.
As of right now my function is affecting all elements with the class name. Test code can be found at my CodePen or below.
I can accomplish this without nested loops as seen here. So my assumption is the problem lies within the second forEach loop. I would appreciate any light on the matter.
Thank you in advance.
/**
*Constructors
**/
const carousel = document.getElementsByClassName("carousel");
const btns = document.getElementsByClassName("btns");
/**
*Execute
**/
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
Array.from(carousel).forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
/*
**Utilities
*/
/*containers*/
.feed-container {
position: absolute;
height: 200px;
width: 100%;
display: grid;
grid-template-columns: 1;
grid-template-rows: 1;
}
.carousel {
grid-row: 1;
grid-column: 1/5;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1;
grid-gap: 15px;
align-self: center;
border: 1px solid #ccc;
overflow-x: scroll;
overflow-y: hidden;
}
/*div-buttons*/
div[class*="slide-"] {
/*opacity: 0;*/
position: sticky;
grid-row: 1;
z-index: 5;
place-self: center;
transition: 0.5s;
padding: 15px;
}
.slide-left {
grid-column: 1;
}
.slide-right {
grid-column: 4;
}
/*items*/
div[class*="item-"] {
grid-row: 1;
width: 400px;
height: 200px;
}
.item-1 {
background: blue;
}
.item-2 {
background: red;
}
.item-3 {
background: grey;
}
.item-4 {
background: yellow;
}
/*scrollbar*/
::-webkit-scrollbar {
display: none;
}
/*chevrons*/
[class*="chevron-"] {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs, 1));
width: 22px;
height: 22px;
border: 2px solid transparent;
border-radius: 25px;
}
[class*="chevron-"]::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 40px;
height: 40px;
border-bottom: 8px solid;
border-left: 8px solid;
bottom: 0;
}
.chevron-left::after {
transform: rotate(45deg);
left: 15px;
}
.chevron-right::after {
transform: rotate(-135deg);
right: 15px;
}
/*
**Exceptions
*/
.btns:hover {
cursor: pointer;
}
.opaque {
opacity: 1 !important;
}
.show {
display: block;
}
<div id="wrapper" style="display:grid; grid-template-rows:repeat(2, auto); grid-gap: 100px;">
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
<br>
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
</div>
It's because you're getting all the elements with class name carousel and then looping through them with each click.
const carousel = document.getElementsByClassName("carousel");
Instead what you need to do is get the carousels only under the button's parent when you trigger the click event
eg something like this:
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
const targetElement = e?.target || e?.srcElement;
const parent = targetElement.parentElement();
const carousel = Array.from(parent.getElementsByClassName("carousel"));
carousel.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
I took a look at the following as recommend and it seems to do the trick.
I created a variable that calls the parentNode "forEach()" button clicked. Oppose to looping through each element.
Working example, codePen
const carousel = document.querySelectorAll(".carousel");
const btns = document.querySelectorAll(".btns");
btns.forEach((i) => {
i.addEventListener("click", () => {
var x = i.parentNode;
var y = Array.from(x.querySelectorAll(".carousel"));
y.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else {
n.scrollLeft += 20;
}
});
});
});
I'm trying to design a component in which you could change the width proportions of two blocks by moving a slider left and right:
codpen and demo:
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 200px;
width: -webkit-calc(50% - 5px);
width: -moz-calc(50% - 5px);
width: calc(50% - 5px);
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: e-resize;
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="slider">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
I have tried using draggable-vue-directive and change the width of blocks based on slider position.
However, it didn't work really well, since draggable-vue-directive set the slider to position:fixed which in turn messed up the block alignment.
How can I make the .slider block horizontally draggable without setting position:fixed?
How to correctly resize Block1 and Block2 when slider moves?
Note: I'm not using jQuery
You can adjust your flexbox along with resize - the downside is that the slider its not very customizeable:
add resize: horizontal to one of the flex items
add flex: 1 to the other flex item (so that this flex item will adjust automatically in response to the changing width of the other flex item as it is resized)
See demo below:
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
resize: horizontal; /* resize horizontal */
overflow: hidden; /* resize works for overflow other than visible */
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
So we'll use vanilla JS instead of the resize solution above:
use a mousedown listener that registers a mousemove listener that updates the block-1 width (and reset the mouseup event)
also consider min-width: 0 to override min-width: auto of the block-2 element
See demo below:
let block = document.querySelector(".block-1"),
slider = document.querySelector(".slider");
slider.onmousedown = function dragMouseDown(e) {
let dragX = e.clientX;
document.onmousemove = function onMouseMove(e) {
block.style.width = block.offsetWidth + e.clientX - dragX + "px";
dragX = e.clientX;
}
// remove mouse-move listener on mouse-up
document.onmouseup = () => document.onmousemove = document.onmouseup = null;
}
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
min-width: 0; /* allow flexing beyond auto width */
overflow: hidden; /* hide overflow on small width */
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: col-resize;
user-select: none; /* disable selection */
text-align: center;
}
<div id="app">
<div class="outer">
<div class="block block-1">
Block 1
</div>
<div class="slider">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
Solution
You can adapt the above into Vue easily without using any custom Vue plugins for this - the changes are:
#mousedown listener on slider that triggers the slider
use of refs to update the width of block-1
See demo below:
new Vue({
el: '#app',
data: {
block1W: '50%'
},
methods: {
drag: function(e) {
let dragX = e.clientX;
let block = this.$refs.block1;
document.onmousemove = function onMouseMove(e) {
block.style.width = block.offsetWidth + e.clientX - dragX + "px";
dragX = e.clientX;
}
// remove mouse-move listener on mouse-up
document.onmouseup = () => document.onmousemove = document.onmouseup = null;
}
}
});
.outer {
display: flex;
flex-direction: row;
}
.block {
height: 100px;
width: 50%; /* 50% would suffice*/
}
.block-1 {
background-color: red;
}
.block-2 {
background-color: green;
flex: 1; /* adjust automatically */
min-width: 0; /* allow flexing beyond auto width */
overflow: hidden; /* hide overflow on small width */
}
.slider {
line-height: 100%;
width: 10px;
background-color: #dee2e6;
border: none;
cursor: col-resize;
user-select: none; /* disable selection */
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="outer">
<div class="block block-1" ref="block1" :style="{'width': block1W}">
Block 1
</div>
<div class="slider" #mousedown="drag">
S<br>l<br>i<br>d<br>e<br>r
</div>
<div class="block block-2">
Block 2
</div>
</div>
</div>
I am trying to keep a seo friendly and semantic structure for my DOM, without repeating whole elements to display them in various positions.
My layout is based on display: flex items. I try to achieve the following:
Important things to know:
I do not want to show/hide divs based on the window width (to avoid unnecessary duplicates)
None of the divs has a known or fixed height
On desktops the divs should be vertical centered, while the right column builds a tag-team (behaves like one single div)
The layout needs to support at least IE11+
Is there a css only solution to achieve this?
If not, it would be easy to cut out the green div and paste its content into the pink one using javascript. But I do have concerns about the performance and "flickering" using this, although resizing the browser makes it more complicated. Do I make this needlessly complicated?
Here is fiddle showing a working solution but with javascript:
CODEPEN DEMO
In general, you can't do this with Flexbox alone, though there might be a compromise based on each given case.
With Flexbox alone, using fixed height, you can accomplish this
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-flow: column wrap;
}
.flex div {
flex: 1;
width: 50%;
}
.flex div:nth-child(2) {
order: -1;
}
.flex::before {
content: '';
height: 100%;
}
#media (max-width:768px) {
.flex div {
width: auto;
}
.flex::before {
display: none;
}
.flex div:nth-child(2) {
order: 0;
}
}
/* styling */
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
}
.flex-child:nth-child(1) {
background: #e6007e;
}
.flex-child:nth-child(2) {
background: #f4997c;
}
.flex-child:nth-child(3) {
background: #86c06b;
}
<div class="flex">
<div class="flex-child">
<div>Top/Right</div>
</div>
<div class="flex-child">
<div>Center/Left</div>
</div>
<div class="flex-child">
<div>Bottom/Right</div>
</div>
</div>
In this case, where no fixed height is allowed, you can combine Flexbox and float.
By set up it for mobile using Flexbox where you add the center item first in the markup and then, with order, move it between the top and bottom.
With a media query you then simply make the flex container a block element and use float to position the left to the left and the right to the right.
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
max-width: 1024px;
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
}
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
padding: 5%;
flex-basis: 33.333%;
display: flex;
align-items: center;
}
.flex-child:nth-child(1) {
background: #e6007e;
order: 1;
}
.flex-child:nth-child(2) {
background: #f4997c;
}
.flex-child:nth-child(3) {
background: #86c06b;
order: 2;
}
#media (min-width: 768px) {
.flex {
display: block;
}
.flex-child {
width: 50%;
}
.flex-child:nth-child(1) {
float: left;
height: 100%;
}
.flex-child:nth-child(2),
.flex-child:nth-child(3) {
float: right;
height: 50%;
}
}
<div class="flex">
<div class="flex-child">
<div>Center/Left</div>
</div>
<div class="flex-child">
<div>Top/Right</div>
</div>
<div class="flex-child">
<div>Bottom/Right</div>
</div>
</div>
Update
Here is another version combining Flexbox with position: absolute, which also vertically center the items in desktop mode
Updated, added a script to control so the absolute positioned element won't get bigger than the right items, and if so, adjust the flex containers height.
Note, the script is by no means optimized, it is only there to show how a fix in certain situations
(function() {
window.addEventListener("resize", resizeThrottler, false);
var fp = document.querySelector('.flex');
var fi = fp.querySelector('.flex-child:nth-child(1)');
var resizeTimeout;
function resizeThrottler() {
// ignore resize events as long as an actualResizeHandler execution is in the queue
if ( !resizeTimeout ) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
actualResizeHandler();
// The actualResizeHandler will execute at a rate of 15fps
}, 66);
}
}
function actualResizeHandler() {
// handle the resize event
if (fp.offsetHeight <= fi.offsetHeight) {
fp.style.cssText = 'height: '+fi.offsetHeight+'px';
} else {
fp.style.cssText = 'height: auto';
}
}
window.addEventListener('load', function() {
actualResizeHandler();
})
}());
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
position: relative;
max-width: 1024px;
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
}
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
padding: 5%;
}
.flex-child:nth-child(1) {
order: 1;
}
.flex-child:nth-child(3) {
order: 2;
}
.flex-child:nth-child(1) div {
background: #e6007e;
}
.flex-child:nth-child(2) div {
background: #f4997c;
}
.flex-child:nth-child(3) div {
background: #86c06b;
}
#media (min-width: 768px) {
.flex {
justify-content: center;
}
.flex-child {
width: 50%;
}
.flex-child:nth-child(1) {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.flex-child:nth-child(n+2) {
margin-left: 50%;
}
}
<div class="flex">
<div class="flex-child">
<div>Center/Left<br>with more<br>content<br>than any<br>of the<br>other items<br>other items<br>other items<br>other items<br>other items</div>
</div>
<div class="flex-child">
<div>Top/Right<br>with more<br>content</div>
</div>
<div class="flex-child">
<div>Bottom/Right<br>with more</div>
</div>
</div>
With script one can also reorder/move items between elements.
Stack snippet
You can also combine this with a media query, and use it to do the actual re-order of the elements
$( document ).ready(function() {
$(window).resize(function() {
if ($( window ).width() < 600 ) {
$(".one").insertBefore("#b");
} else {
$(".one").insertBefore(".two");
}
});
});
.outer, #flex, #flex2 {
display: flex;
flex-direction: column;
}
#a {
order: 4;
background: #ccc;
}
#b {
order: 1;
background: #aaa;
}
#c {
order: 3;
background: #d33;
}
.one {
order: 2;
background: #aaa;
}
.two {
order: 5;
background: #aaa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="outer">
<div id="flex">
<div id="a">A</div>
<div id="b">B</div>
<div id="c">C</div>
</div>
<div id="flex2">
<div class="one">Show me 2nd</div>
<div class="two">Show me 5th</div>
</div>
</div>
Update 2 (answered at another question but later moved here)
If we talk about smaller items, like a header or smaller menus, one can do what many website platform providers like "squarespace", "weebly", "wordpress", etc does. Their templates holds different markup structures, where an item sometimes exist twice, one visible for desktop, another for mobile.
Also, being so small, there will be less to nothing when it comes to performance (and personally I don't see anymore issue with this than having duplicate CSS rules, one for each screen size, and happily do this instead of introducing script).
Fiddle demo
Stack snippet
.container {
display: flex;
}
.container > div {
width: 50%;
}
.container div:nth-child(-n+2) {
border: dashed;
padding: 10px;
}
.container > div:nth-child(1) {
display: none; /* hide outer "Flower" */
}
#media (max-width:768px) {
.container {
flex-direction: column;
}
.container div {
width: auto;
}
.container div:nth-child(1) {
display: block; /* show outer "Flower" */
}
.container div:nth-child(3) div:nth-child(1) {
display: none; /* hide inner "Flower" */
}
}
<div class="container">
<div>Flower</div>
<div>Tree</div>
<div>
<div>Flower</div>
<div>Bee</div>
</div>
</div>
I'm new to jQuery and am struggling with making the jQuery detect the location of div .stage-O so that when scrolling down the .header doesn't disappear until the bottom of that .stage-O hits the top of the page?
jQuery(document).ready(function () {
var lastFixPos = 0,
threshold = 100, //sensitivity on scrolling
$header = $('.header');
$(window).on('scroll', function() {
var st = $(this).scrollTop();
var diff = Math.abs($(window).scrollTop() - lastFixPos);
if (diff > threshold || st < 100) {
if (st < lastFixPos) {
// scroll up
$header.removeClass('hide').addClass('color headerBGchange headerLIchange');
}
lastFixPos = st;
} else if (st > lastFixPos) {
//scroll down
$header.addClass('hide').removeClass('color');
}
});
$(window).scroll(function(e) {
var sw = $('.header'),
pg = $('.stage-2'),
diff = pg[0].offsetbottom - window.pageYOffset;
sw.css('background-color', diff < 100 ? 'white' : '');
});
});
.header {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 80px;
-webkit-transition: top 250ms ease;
transition: top 250ms ease;
position: fixed;
width: 100%;
top: 0;
background-color: transparent;
overflow: hidden;
}
.header ul {
margin: 20px;
padding: 0;
}
.header ul li {
display: inline-block;
margin-right: 20px;
color: green;
}
.header ul li:last-child {
margin-right: 0;
}
.hide {
top: -80px;
}
.headerBGchange{
Background: white;
}
.headerLIchange{
color: Blue;
}
.stage {
color: #fff;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 100vh;
background-color: bisque;
font-size: 48px;
}
.stage-0 {
background: black;
}
.stage-1 {
background: #030202;
}
.stage-2 {
background: #060505;
}
.stage-3 {
background: #080707;
}
.stage-4 {
background: #0b0a09;
}
.stage-5 {
background: #0e0c0b;
}
.stage-6 {
background: #110e0e;
}
.stage-7 {
background: #141110;
}
.stage-8 {
background: #161312;
}
.stage-9 {
background: #191515;
}
.stage-10 {
background: #1c1817;
}
.stage-11 {
background: #1f1a19;
}
.stage-12 {
background: #221d1c;
}
.stage-13 {
background: #241f1e;
}
.stage-14 {
background: #272120;
}
.stage-15 {
background: #2a2422;
}
.stage-16 {
background: #2d2625;
}
.stage-17 {
background: #302827;
}
.stage-18 {
background: #322b29;
}
.stage-19 {
background: #352d2c;
}
.stage-20 {
background: #38302e;
}
.stage-21 {
background: #3b3230;
}
.stage-22 {
background: #3e3432;
}
.stage-23 {
background: #413735;
}
.stage-24 {
background: #433937;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class="header">
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</div>
<div class="stage stage-0">1</div>
<div class="stage stage-2">3</div>
<div class="stage stage-4">5</div>
<div class="stage stage-6">7</div>
<div class="stage stage-8">9</div>
<div class="stage stage-10">11</div>
<div class="stage stage-12">13</div>
<div class="stage stage-14">15</div>
<div class="stage stage-16">17</div>
<div class="stage stage-18">19</div>
<div class="stage stage-20">21</div>
<div class="stage stage-22">23</div>
Is this what you are looking for? I changed the code quite a bit because it seemed like yours was a little overly complicated. Not sure why you were attaching two scroll events. Also I just added a red border to the stage class so you could clearly see when we were passing the bottom of it.
Fiddle: http://jsfiddle.net/AtheistP3ace/4hs7n0Lq/
var lastScrollTop = 0;
$(window).on('scroll', function() {
var header = $('.header');
var stage0 = $('.stage-0');
var scrollTop = $(window).scrollTop();
if (scrollTop > lastScrollTop) {
// down scroll
if (scrollTop > stage0.offset().top + stage0.height()) {
header.addClass('hide').removeClass('color');
}
} else {
// up scroll
if (scrollTop <= stage0.offset().top + stage0.height()) {
header.removeClass('color headerBGchange headerLIchange');
} else {
header.removeClass('hide').addClass('color headerBGchange headerLIchange');
}
}
lastScrollTop = scrollTop;
});
It simply tracks the lastScroll to determine if we are going up or down. If we are going down lets check if we have passed the stage0 div by getting its offset plus its height (the bottom of it). If we are scrolling up lets see if we are above the bottom of the stage0 div, if not we are scrolling up but have not reached it yet.
As to your question about the text color its not working because you set the color on the header which would cascade down but you also have this:
.header ul li {
display: inline-block;
margin-right: 20px;
color: green;
}
Which is a more specific selector so it overrides the higher one. So instead of
.headerLIchange {
color: Blue;
}
do
.header.headerLIchange ul li {
color: Blue;
}
Fiddle: http://jsfiddle.net/AtheistP3ace/4hs7n0Lq/1/
This might help you:
<body onload="document.getElementById('scrollBox').scrollTop = document.getElementById('scrollPosition').value;">
<input type="hidden" id="scrollPosition" />
<div id="scrollBox" style="overflow:scroll;height:100px;width:150px;" onscroll="javascript:document.getElementById('scrollPosition').value = this.scrollTop">
...content goes here...
...more content...
...link goes here...
</div>
</body>
Ref: http://www.quackit.com/html/codes/div_scroll_position.cfm