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>
Related
I can't figure out why I'm getting this little bit of green when the window is an odd number of pixels wide. I think it has something to do with sub-pixel rendering, but I'm just not sure where the green is coming from. It's just the 2nd div too which is weird.
I have some script that is animating the BG of this div. I'm sure this is part of the issue, but I can't figure out why it's only happening to my 2nd div.
I tried to manually set the width of this div, but I was hoping it would be responsive and scale with the window size.
let currentStage = 1
function performAction(selectedStage) {
currentStage = selectedStage
let stages = document.body.getElementsByClassName('stage-flow-item')
let stageLines = document.body.getElementsByClassName('stage-flow-line')
console.log("selectedStage: " + selectedStage)
for (let stage of stages) {
if (stage.id > currentStage) {
stage.classList.remove('completed')
stage.classList.add('active')
} else {
stage.classList.remove('active')
stage.classList.add('completed')
}
}
for (let stageLine of stageLines) {
if (stageLine.id > currentStage) {
stageLine.classList.remove('lineCompleted')
stageLine.classList.add('lineActive')
} else {
stageLine.classList.remove('lineActive')
stageLine.classList.add('lineCompleted')
}
}
}
.stage-flow-container {
display: flex;
justify-content: space-between;
align-items: center;
height: 70px;
padding: 0 30px;
}
.stage-flow-item {
width: 70px;
height: 70px;
min-width: 70px;
border-radius: 50%;
background-color: #ddd;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: #fff;
cursor: pointer;
}
.stage-flow-item.active {
background-color: #ddd;
}
.stage-flow-item.completed {
background-color: #6ab04c;
}
.stage-flow-line {
width: calc(100vw);
height: 6px;
background-color: #ddd;
/* default color */
background: linear-gradient(to left, #ddd 50%, #6ab04c 50%) right;
position: relative;
background-size: 200%;
transition: .5s ease-out;
}
.stage-flow-line.lineCompleted {
background-position: left;
background-color: #6ab04c;
}
.stage-flow-line.lineActive {
background-position: right;
background-color: #ddd;
}
<div class="stage-flow-container">
<div id=1 class="stage-flow-item" onclick="performAction(1)">1</div>
<div id=1 class="stage-flow-line"></div>
<div id=2 class="stage-flow-item" onclick="performAction(2)">2</div>
<div id=2 class="stage-flow-line"></div>
<div id=3 class="stage-flow-item" onclick="performAction(3)">3</div>
</div>
I'm not sure if this is on the right track, but I'd eliminate the odd 100vw width on the connectors and instead make them flex. I'd then remove the 200% background size multiplier. By setting the gradient points to 100% the problem is gone. I really don't know if this covers your use case, though.
I converted from background gradient to a pseudo-element solution for the color transition. I think it's simpler. You'd probably have to use CSS animations (as opposed to simple transitions) to make it work otherwise. Of course, you could apply the same principle to the stage items as well, implementing a delay to crate a consistent animation across the item and the line.
Note that duplicated ID values are invalid in HTML. They must be unique. I've refactored to use data attributes instead and an event listener instead of inline JavaScript.
const stageEls = document.querySelectorAll('.stage-flow-item')
const lineEls = document.querySelectorAll('.stage-flow-line')
let currentStage = 1
stageEls.forEach(el => {
el.addEventListener('click', () => {
performAction(el.dataset.stage)
})
})
function performAction(selectedStage) {
currentStage = selectedStage
for (let el of stageEls) {
if (el.dataset.stage > currentStage) {
el.classList.remove('completed')
el.classList.add('active')
} else {
el.classList.remove('active')
el.classList.add('completed')
}
}
for (let el of lineEls) {
if (el.dataset.stage > currentStage) {
el.classList.remove('lineCompleted')
el.classList.add('lineActive')
} else {
el.classList.remove('lineActive')
el.classList.add('lineCompleted')
}
}
}
.stage-flow-container {
display: flex;
align-items: center;
height: 70px;
padding: 0 30px;
}
.stage-flow-item {
width: 70px;
height: 70px;
min-width: 70px;
border-radius: 50%;
background-color: #ddd;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: #fff;
cursor: pointer;
}
.stage-flow-item.active {
background-color: #ddd;
}
.stage-flow-item.completed {
background-color: #6ab04c;
}
.stage-flow-line {
flex: 1;
height: 6px;
background: #ddd;
position: relative;
}
.stage-flow-line::after {
position: absolute;
content: '';
top: 0;
left: 0;
width: 0;
height: 100%;
background: #6ab04c;
transition: all 0.5s ease-out;
}
.stage-flow-line.lineCompleted::after {
width: 100%;
}
<div class="stage-flow-container">
<div data-stage=1 class="stage-flow-item">1</div>
<div data-stage=1 class="stage-flow-line"></div>
<div data-stage=2 class="stage-flow-item">2</div>
<div data-stage=2 class="stage-flow-line"></div>
<div data-stage=3 class="stage-flow-item">3</div>
</div>
I'm trying to make it so that when you click on the button with the class .booking__button, the block scrolls up under the header. Position should not change, only scroll. This is done so that the search results of the booking module, which, would be visible to the user. I found the code that does the scrolling, but it works with the exact number, now 100px, but you understand that this distance will be different for everyone, depending on the height of the screen.
document.querySelector('.booking__button').addEventListener('click', () => {
window.scrollTo(0, 100);
});
body {
margin: 0;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: #002164;
}
.hero {
min-height: calc(100vh - 100px);
background: #fff;
}
.booking__module {
display: flex;
justify-content: center;
align-items: center;
background: #BC0B3C;
}
.booking__search {
height: 600px;
background: #ccc;
}
.booking__button {
height: 20px;
margin: 40px;
}
.others {
height: 200vh;
}
<header class="header"></header>
<main class="hero"></main>
<section class="booking">
<div class="booking__module">
<button class="booking__button">booking</button>
</div>
<div class="booking__search"></div>
</section>
<section class="others"></section>
One approach is below, with explanatory comments in the code. Note that while I changed the background-color of the <header> element, that's simply to visualise the functionality and is not at all required:
// we pass a reference to the Event Object ('evt') to the function:
document.querySelector('.booking__button').addEventListener('click', (evt) => {
// we retrieve the closest ancestor <section> element of the element
// to which the event-handler is bound, and retrieve the 'top' property
// of its bounding-client rect:
let {top} = evt.currentTarget.closest('section').getBoundingClientRect();
// we then scroll to that value:
window.scrollTo(0, top);
});
body {
margin: 0;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
/*background: #002164;*/
background-color: hsl(200deg 70% 70% / 0.4);
}
.hero {
min-height: calc(100vh - 100px);
background: #fff;
}
.booking__module {
display: flex;
justify-content: center;
align-items: center;
background: #BC0B3C;
}
.booking__search {
height: 600px;
background: #ccc;
}
.booking__button {
height: 20px;
margin: 40px;
}
.others {
height: 200vh;
}
<header class="header"></header>
<main class="hero"></main>
<section class="booking">
<div class="booking__module">
<button class="booking__button">booking</button>
</div>
<div class="booking__search"></div>
</section>
<section class="others"></section>
JS Fiddle demo.
References:
Element.closest().
Element.getBoundingClientRect().
Event.
Event.currentTarget.
EventTarget.addEventListener().
Window.scrollTo.
whole code
With Javascript I want to remove all current Elements on the Screen exept the text and it's CSS styles. My end goal is that I can essentially exchange the text "Bubble" with "Bounce" and still have the same CSS styling in the end. But as I also need to remove ALL Elements from the screen to run the next code I need to clear out the body and CSS entirely. This leads to my Problem. I dont know how to either get the same CSS styling back after clearing it out nor how to exclude the CSS Styling from the clearing. Can anybody help?
document.getElementById("text").innerHTML = "Bubble";
document.addEventListener("click", next);
function next() {
document.head.innerHTML = " ";
document.getElementById("text").innerHTML = "Bounce";
}
section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
content {
min-width: 100%;
max-width: 100%;
margin-left: auto;
margin-right: auto;
text-align: center;
position: absolute;
left: 0;
right: 0
}
section h2 {
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
}
<section>
<div class="content">
<h2 id="text"></h2>
</div>
</section>
If you really need to call document.head.innerHTML = " "; this is one way to do it :
I created a function called add_css() which adds the CSS (which is now stored in a variable) in a style tag in the head of document.
Also your CSS had a typo (content instead of .content)
document.getElementById("text").innerHTML = "Bubble";
const css_to_keep = `section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.content {
min-width: 100%;
max-width: 100%;
margin-left: auto;
margin-right: auto;
text-align: center;
position: absolute;
left: 0;
right: 0
}
section h2 {
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
}`;
document.addEventListener("click", next);
add_css();
function next() {
document.head.innerHTML = " ";
add_css();
document.getElementById("text").innerHTML = "Bounce";
}
function add_css(){
const style_elem = document.createElement('style');
document.head.appendChild(style_elem);
style_elem.type = 'text/css';
style_elem.appendChild(document.createTextNode(css_to_keep));
}
<section>
<div class="content">
<h2 id="text"></h2>
</div>
</section>
You don't need to "remove" anything - just replace the innerHTML of the document body with a new updated section.
// A function that returns a string
// (See template strings below)
function createSection(text) {
return `
<section>
<div class="content">
<h2 id="text">${text}</h2>
</div>
</section>
`;
}
document.body.innerHTML = createSection('Bubble');
document.addEventListener('click', next);
// Replace the body HTML with the new section
function next() {
document.body.innerHTML = createSection('Bounce');
}
section{width:100%;height:100vh;overflow:hidden;background:#1f69fa;display:flex;justify-content:center;align-items:center;flex-direction:column}content{min-width:100%;max-width:100%;margin-left:auto;margin-right:auto;text-align:center;position:absolute;left:0;right:0}section h2{font-size:10em;color:#333;margin:0 auto;text-align:center;font-family:consolas}
Additional documentation
Template/string literals
I have a problem with a css animation im trying to add to each child inside a nav tag
I'm trying to create a moving stock panel
The weird thing about it is that if I add another css attribute like color it does work
Does anyone knows how to solve this
function DoAnimation(animation, a) {
var parent = a.parentElement
if (animation != null) {
parent.removeChild(a);
parent.appendChild(a);
console.log(1);
a.style.animationName = animation;
a.style.animationDuration = "2s";
}
}
#keyframes MoveStockTORIght {
from {
left: 0;
}
to {
transform: translateX(100px);
}
}
.SiteNav {
overflow: hidden;
text-align: center;
margin: auto;
text-align: center;
}
.SiteNav a {
gap: 10%;
padding: 1%;
}
<nav class="SiteNav" id="siteNavigantion">
MSFT 294.23
CLOV 8.06
LCID 22.87
SAVA 51.08
AAL 20.13
AMZN 3246.3
NFLX 627.04
GOOG 2776.95
AAPL 142.81
NVDA 206.95
</nav>
Why not just use Marquee?
.SiteNav {
overflow: hidden;
text-align: center;
margin: auto;
text-align: center;
}
.SiteNav a {
gap: 10%;
padding: 1%;
}
marquee a { text-decoration: none; color:green; }
<marquee class="SiteNav" id="siteNavigantion">
MSFT 294.23
CLOV 8.06
LCID 22.87
SAVA 51.08
AAL 20.13
AMZN 3246.3
NFLX 627.04
GOOG 2776.95
AAPL 142.81
NVDA 206.95
</marquee>
I'm trying to get a div to render until the bottom of the screen/page (and no further than that).
I'm able to do that successfully as long as this div is the only thing in the entire page. However, it's overflowing the page whenever I have anything above it (in this example, the text Some Header).
https://codesandbox.io/s/keen-bird-phx1f?file=/src/App.js
The React code:
import "./styles.css";
import React, { Fragment } from "react";
export default function App(props) {
return (
<Fragment>
<div>Some Header</div>
<div className="test1">
<div className="dashbar">Dash</div>
<div className="other-content">Other</div>
</div>
</Fragment>
);
}
The CSS code:
* {
box-sizing: border-box;
}
body {
margin: 0;
}
.test1 {
display: flex;
height: 100vh;
border: 10px solid green;
}
.dashbar {
flex: 1 0 10%;
background: dodgerblue;
}
.other-content {
flex: 1 0 90%;
background: papayawhip;
}
How can I make it so that this div will stop at the bottom of the page and not overflow like this?
If I remove <div>Some Header</div>, then it behaves as expected:
.test1 has a height of 100vh, meaning you are forcing it to be exactly as tall as the viewport. But it has a header above it! So, the header necessarily pushes it down and it overflows the screen. You need a bit more of Flexbox trickery to get it right, especially flex-grow:1.
* {
box-sizing: border-box;
}
body {
margin: 0;
}
#root {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}
.test1 {
display: flex;
flex-grow: 1;
border: 10px solid green;
}
.dashbar {
flex: 1 0 10%;
background: dodgerblue;
}
.other-content {
flex: 1 0 90%;
background: papayawhip;
}
<body>
<div id="root">
<div>
Some Header<br>
with a couple<br>
of lines in it
</div>
<div class="test1">
<div class="dashbar">Dash</div>
<div class="other-content">Other</div>
</div>
</div>
</body>
It's because "test1" has height as 100vh and there is a static header text which has some height. Try this to fix:
* {
box-sizing: border-box;
line-height: 15px;
}
.test1 {
display: flex;
height: calc(100vh - 15px);
border: 10px solid green;
}
--- Dynamic solution:
https://codesandbox.io/s/stupefied-agnesi-x496f?file=/src/App.js