I am working on a layout that is similar in nature to a slot machine. Each of its children will be text, and the layout is responsive (so at different resolutions, the children's text may or may not wrap to multiple lines, thus changing the height).
The layout will have a visible "viewport" of 3 items at a time. So that the "viewport" does not shift around, all children need to have the same height. This got me thinking of 2 different routes, but I can't make either work.
Use display: flex. With a column layout, you can have them all stack. The problem with this is, I don't think you can make all children have the same height unless you specify a calculated height on the flex container (thus, you'd have to use js to calculate the max height of all the flex children)
Use display: grid. Out of the box you can make all children have equal height with grid. The problem is, how do hide the overflow guaranteeing you are only showing 3 at a time in the "viewport" for the slot machine?
This layout may not be possible without js calculations, but because of the fact that it has to be completely responsive, I don't want to have to redo the calculations on every window resize. Can anyone think of a way to do this using pure css?
I made a fiddle to show a bare-bones implementation. The "viewport" is the red box, and each item is in the blue box. In the real world, I would be hiding everything outside of the red box and would not want to have to specifically set height on any container.
var scroll = document.getElementById('scroll');
var wrapper = document.getElementById('wrapper');
scroll.addEventListener('click', function() {
wrapper.classList.toggle('scrolled');
});
.example {
display: flex;
}
.container {
height: 90px;
width: 300px;
outline: 1px solid red;
z-index:1;
}
.wrapper {
display: flex;
flex-direction: column;
transition-property: transform;
transition-duration: 500ms;
}
.wrapper.scrolled {
transform: translateY(-300px);
}
.inner {
outline: 1px solid blue;
flex-grow: 1;
height: 30px;
}
.buttons {
margin-right: 3em;
}
<div class="example">
<div class="buttons">
<button id="scroll">Scroll</button>
</div>
<div class="container">
<div id="wrapper" class="wrapper">
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
<div class="inner">Some text</div>
</div>
</div>
</div>
Hey so I think I get what you're going for here and designed a "slot machine" mechanic that scales its inner and outer objects using vh.
So the container object would have something like...
.container {
width: 300px;
height: 90vh;
outline: 1px solid red;
z-index:1;
overflow: hidden;
}
...and the inner object would have something like...
.inner {
outline: 1px solid blue;
flex-grow: 1;
height: 30vh;
}
I also adjusted the Javascript scrolling to base the amount scrolled off of what object is being scrolled to. This can be automated with something like a timer instead of the button:
var scroll = document.getElementById('scroll');
var wrapper = document.getElementById('wrapper');
var innerArray = document.getElementsByClassName('inner');
var innerNum = 1; // Start at the second element
scroll.addEventListener('click', function() {
if (innerNum == innerArray.length-2) { // Check to see if the last item has been scrolled to
innerNum = 0; // Scroll back to the first item if it has
}
//wrapper.classList.toggle('scrolled');
innerArray[innerNum].scrollIntoView({
behavior: 'smooth'
});
innerNum++;
});
Here is the Codepen that demonstrates the example. I can clarify the code further if you would like me to. My apologies if I was way off the ball on what you were going for!
Related
I'm making a small website built entirely with flexbox columns and rows. One of the rows is horizontally scrollable — howewer, I want it to be scrolled not only with scrolling, but also by clicking either on left or right half of the visible part of the row. Plus, hovering on the left side should change cursor to cursor: w-resize and on the left side — to cursor: e-resize.
see screenshot
see a sketch of what i'm trying to achieve
<div class="project">
<div class="project-images">
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
<div id="project-image"><img id="image-standart-height" style="max-height: 400px" src="/Users/andrei/Desktop/All Work/portfolio and cv/my website/images/bahorvesna.png" alt=""></div>
</div>
<div class="project-text">
Bahor/Vesna
</div>
</div>
#project-image {
flex: 0 0 auto;
max-height: 400px;
}
#image-standart-height {
max-height: 400px;
}
.project-text {
display: flex;
gap: 20px;
align-items: baseline;
}
I was thinking of creating an invisible layer on a different z-axis level, but I assume it would be difficult not to ruin the flexbox.
Is it possible to make a function in JS to detect when a mouse is entering a certain area of the visible part of the div, change cursor and make the area clickable?
You can also do it without adding any extra div's, by checking the position of the mouse pointer relative to the container. Try it out by clicking the left and right half of the box in the example.
If you have multiple elements, all you need to do is loop the set of elements and put the two event listeners inside the loop.
const containers = document.querySelectorAll('.container');
containers.forEach((container) => {
container.addEventListener('click', (e) => {
const clientRect = e.currentTarget.getBoundingClientRect();
if ((e.pageX - clientRect.left) < clientRect.width / 2)
console.log('Clicked left half');
else
console.log('Clicked right half');
});
container.addEventListener('mousemove', (e) => {
const clientRect = e.currentTarget.getBoundingClientRect();
if ((e.pageX - clientRect.left) < clientRect.width / 2)
container.style.cursor = 'w-resize';
else
container.style.cursor = 'e-resize';
});
});
.container {
width: 300px;
height: 100px;
border: 1px solid #000;
margin: 50px auto;
}
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>
<div class="container"></div>
I have 4 dropdown containers. When i am clicking on a header , i want its associated paragraph to appear and other paragraph, that had been appeared ,disappear.
When a header is clicked, i remove active class from all the other paragraphs and add it to the paragraph that its header is clicked. It works fine but the problem is that first the current paragraph appears and then other paragraph disappears but i want them to work synchronously like while one appears another disappears but i do not know how to do that.
HTML:
<div class="dropDown">
<div class="header">
header1
</div>
<p class="active">some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header2
</div>
<p>some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header3
</div>
<p>some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header4
</div>
<p>some things here some things here some things here some things here</p>
</div>
CSS:
.dropDown p{
background: rgb(245, 245, 245);
border-right: 40px solid #e8e8e8;
font-size: 13px;
line-height: 35px;
max-height: 0px;
overflow: hidden;
margin-bottom: 0;
padding: 0px 15px 0px 30px;
transition: max-height .3s ease;
}
.dropDown p.active{
max-height: 500px;
padding-top:8px;
padding-bottom: 20px;
}
jQuery:
Headers.click(function(){
var theP = $(this).parent().children("p"); //current paragraph
dropDownParagrsphs.not(theP).removeClass("active");
theP.toggleClass("active");
});
How can i make the transitions to work together like while one paragraph's height decreases , other paragraph's height increases?
interestingly, you've stumbled on a deceptively difficult problem in pure CSS.
The truth is, your paragraphs are already behaving as you want them to, the problem is that you've specified a large max height relative to the actual content of the p, it gives the impression that they are executed one after the other, but that's just because the time it takes is relatively (compared to actual height of p with overflow: hidden) long to grow/shrink max-height to 500px. It's as if you have an invisible box growing to 500px.
This should be easily solvable by changing your max-height to auto, but unfortunately you cannot animate height auto in pure CSS transitions. your options are:
a) choose a different hardcoded max-height which is closer to the actual content size.
b) use transform scale(Y)
c) use pure JS: for example slideUp and slideDown
var Headers = $('.header')
var dropDownParagraphs = $('.dropDown p')
Headers.click(function(){
var theP = $(this).parent().children("p"); //current paragraph
// theP.addClass("active");
// dropDownParagraphs.not(theP).removeClass("active");
dropDownParagraphs.not(theP).slideUp(200);
theP.slideDown(200);
});
check this codepen for implementation of c)
https://codepen.io/bakuthe3rd/pen/abvzVJz
You can use .slideToogle(duration, easing, callback) method to do so. Also, I've shifted padding-bottom and padding-top properties to p from p.active, so that they don't change dramatically during the transition.
jQuery:
$('p').slideUp(0); // close all
$('.active').slideToggle(300); // open the default
$('.header').click(function() {
var nowP = $(this).parent().children("p"); // current paragraph
var prevP = $('.active'); // opened paragraph
var isSame = $('p').index(prevP) == $('p').index(nowP);
prevP.removeClass("active").slideToggle({ duration: 300, queue: false });
if (!isSame) nowP.addClass("active").slideToggle({ duration: 300, queue: false });
});
$('p').slideUp(0);
$('.active').slideToggle(300);
$('.header').click(function() {
var nowP = $(this).parent().children("p"); // current paragraph
var prevP = $('.active'); // opened paragraph
var isSame = $('p').index(prevP) == $('p').index(nowP);
prevP.removeClass("active").slideToggle({ duration: 300, queue: false });
if (!isSame) nowP.addClass("active").slideToggle({ duration: 300, queue: false });
});
.dropDown p {
background: rgb(245, 245, 245);
border-right: 40px solid #e8e8e8;
font-size: 13px;
line-height: 35px;
overflow: hidden;
margin-bottom: 0;
padding: 0px 15px 0px 30px;
transition: max-height .3s ease;
padding-top: 8px;
padding-bottom: 20px;
}
.dropDown p.active {
max-height: 500px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="dropDown">
<div class="header">
header1
</div>
<p class="active">some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header2
</div>
<p>some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header3
</div>
<p>some things here some things here some things here some things here</p>
</div>
<div class="dropDown">
<div class="header">
header4
</div>
<p>some things here some things here some things here some things here</p>
</div>
Also, you'll notice that on clicking the same link, the dropdown will be closed. I've added this as I expect this is probably the desired effect :)
Further, you can add 'linear' as second argument in .slideToggle method, if you need a linear transition. By default, it is 'swing'.
I'm using display flex to display multiple items in one big container (parentDiv). The code is working fine but I get big problems with horizontal centering the items (especially If there are only a few items they should get horizontally centered) so I was using justify-content what leads to big issues:
The parent div is not able to display all items anymore. The first item that gets displayed is the item "04" while it should be "01". How to avoid this?
Please have a look at this code:
#bigDiv {
position: absolute;
width: 100%;
height: 100%;
}
#parentDiv {
width: 90%;
height: 50%;
overflow-y: hidden;
overflow-x: scroll;
text-align: center;
white-space: nowrap;
background: red;
display: flex;
justify-content: center;
align-items: center;
}
.item {
color: white;
background: blue;
flex: 0 0 4%;
margin: 0 3%;
}
.item::after {
content: "";
display: block;
padding-bottom: 100%;
}
<div id="bigDiv">
<div id="parentDiv">
<div class="item">01</div>
<div class="item">02</div>
<div class="item">03</div>
<div class="item">04</div>
<div class="item">05</div>
<div class="item">06</div>
<div class="item">07</div>
<div class="item">08</div>
<div class="item">09</div>
<div class="item">10</div>
<div class="item">11</div>
<div class="item">12</div>
<div class="item">13</div>
<div class="item">14</div>
<div class="item">15</div>
<div class="item">16</div>
</div>
</div>
See this image:
My intentions: The parent div should be able to show all of the items (starting with "01" - and the last element should be the "16"-one)
Note: If there are only 4 or less items they should get centered horizontally. (The reason why I added justify-content).
You're fiting 160% into 100%. And you want it centered. And it works: the 160% total width of the resulting children is nicely centered.
But you're also expecting whatever is outside the parent to be accessible.
It's pretty much like making a child element go outside of its parent by -30% to the left or to the top (by any other method) and expecting the parent to allow you to scroll to it. It's not going to happen!
If it did, the child would no longer be placed at -30%, it would be placed at 0%. Scrollbars will never scroll to left or top negative space. It's by design. You need to take it into consideration when designing your page.
Whenever you center a bigger child into a smaller parent you won't be able to use parent's scrollbars to scroll to the beginning of the child. So anything preventing the child positioning in the parent's left negative space will fix it.
JSFiddle : https://jsfiddle.net/ttpkfs9s/
I have a UI component that should arrange elements into a row and displays them with elements on the left and on the right, with the active element being in the middle:
[1][2][3] [4] [5][6][7][8][9]
So far I have been achieving this by floating elements left and right, while keeping the one in the middle float: none; (this is good enough).
However, way too late into implementing the navigation JS I realised that I've made a huge mistake, and that the actual order the elements are displayed in are as follows:
[1][2][3] [4] [9][8][7][6][5]
Which is a huge problem as these elements are supposed to be clickable /facepalm
Are there any at most not too invasive CSS/HTML options I can use to get the displayed order correct?
EDIT: I missed the part about you needing the active div to always be in the center of the row.
You could contain the div's inside a container, and float the container insted, but that would probably be hard to do.
I took the liberty of changing things up abit, maybe you can use it, maybe u can't.
I set all items to the same width, and made a function for resizing the div's after u click one of the items.
https://jsfiddle.net/ttpkfs9s/1/
html
<div class="row">
<div class="item left">1</div>
<div class="item left">2</div>
<div class="item left">3</div>
<div class="item left">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
<div class="item">9</div>
<div class="item">10</div>
css
.row {
height: 150px;
background: blue;
width: 100%;
}
.item {
float: left;
padding: 2.5px;
color: white;
width: 9.4%;
height: 100%;
background: red;
margin: 0 0.3%;
box-sizing: border-box;
transition: 0.7s linear;
}
.active {
color: black;
background: yellow;
}
js
function setWidth(){
if($(".item").hasClass("active")){
$(".item").width("6%");
$(".active").width("40%");
};
}
$(".item").click(function(){
$(".item").removeClass("active");
$(this).addClass("active");
setWidth();
})
I am having this simple but frustrating CSS problem. I am trying to fill a page completely with divs/boxes. The problem is, that these boxes have same width all the time, therefore boxes won't fill up evenly. Let me demonstrate this:
Fiddle.
CSS:
.box {
float: left;
width: 200px;
height: 200px;
margin: 0px 10px 10px 0px;
background-color: #000000;
}
HTML:
<div id="boxes">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
This is OK:
But if user has a smaller window size, it will go like:
This would be the best solution, resizing boxes to fit screen horizontally evenly:
What would be the best solution to make them fit whole page when window is resized? Preferably CSS-only (if possible).
Here is a working example.
IMO, the solution depends om your needs. If are able to scale the images, then the 33% rule offered by others here is probably acceptable. However, you may wish to limit how far the size can be go (min-width). Also, this is going to totally bork your aspect ratios, which you might find unacceptable.
One alternate solution would be to have a 'viewport' div on on-top of a larger div that allows some bleed-over to minimize the 'gosh, it's only one pixel only over' effect (where you get a huge, gaping blank column. This solution allows the cards to bleed out of the viewport a bit before forcing a new column. And it is CSS-only. This is the provided example. Test it out by changing the outer container width and height ('my-outer'):
/* CSS */
.my-outer {
position : relative;
box-sizing : border-box;
width : 350px;
height : 450px;
border : 1px solid red;
overflow-x : hidden;
overflow-y : auto;
}
.my-inner {
position : relative;
box-sizing : border-box;
border : 1px solid green;
width : 120%;
}
.my-card {
box-sizing : border-box;
float : left;
margin : 10px;
width : 100px;
height : 100px;
font-size : 50px;
line-height : 100px;
text-align : center;
font-weight : 800;
background : #aaf;
color : #fff;
border-radius : 2x;
box-shadow: 0 0 16px -2px #888;
}
<!-- HTML -->
<div class="my-outer">
<div class="my-inner">
<div class="my-card">1</div>
<div class="my-card">2</div>
<div class="my-card">3</div>
<div class="my-card">4</div>
<div class="my-card">5</div>
<div class="my-card">6</div>
<div class="my-card">7</div>
<div class="my-card">8</div>
<div class="my-card">9</div>
<div class="my-card">10</div>
<div class="my-card">11</div>
<div class="my-card">12</div>
<div class="my-card">13</div>
<div class="my-card">14</div>
<div stlye="clear : both;"></div>
</div>
Another alternate solution, which would probably provide the best user experience, would be to resize the container in steps. This would require some JavaScript, but can be fairly easy to quite difficult to implement depending about the environment. And yes, I have done this sort of thing before ;)
Try giving the container div boxes a width of the max amount of boxes + the margin for each box. In your example that would be 200 * 3 + 30. This is given that you only want three boxes per row.
something like
html:
<div class="box></div>
<div class="box></div>
<div class="box></div>
<div class="box></div>
<div class="box></div>
<div class="box></div>
css:
.box{
height: 100px;
width: 33%;
}