Is there any way to have a transition on the order of flex-box items?
In other words, can I have this (details in this fiddle)
#container {
display: flex;
}
#container:hover div:last-child {
order: -1;
}
animated (the element getting the new position assumes it's position over time), please?
I am not really answering the question because I am not using the order property.
But I wanted to do something similar to what you expect, and finally decided to :
In HTML, add a data-order attribute to the elements
Add the CSS properties for each element position
Change the data-order using Javascript
Using CSS transitions for the interpolation
setInterval(changeOrder, 3000);
function changeOrder() {
const allSlides = document.querySelectorAll(".single-slide");
const previous = "1";
const current = "2";
const next = "3";
for (const slide of allSlides) {
const order = slide.getAttribute("data-order");
switch (order) {
case current:
slide.setAttribute("data-order", previous);
break;
case next:
slide.setAttribute("data-order", current);
break;
case previous:
slide.setAttribute("data-order", next);
break;
}
}
}
.all-slides {
display: flex;
width: 80vw;
margin: 0 auto;
perspective: 500px;
height: 500px;
}
.single-slide {
padding: 30px 20px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
width: 30%;
position: absolute;
background-color: white;
transition: 2s ease;
box-shadow: 0px 5px 10px lightgrey;
}
/* Left slide*/
.single-slide[data-order="1"] {
left: 10vw;
transform: translate(-50%) scale(0.8, 0.8);
z-index: 1;
opacity: 0.7;
}
/* Middle slide */
.single-slide[data-order="2"] {
left: 40vw;
transform: translate(-50%);
z-index: 3;
opacity: 1;
}
/* Right slide*/
.single-slide[data-order="3"] {
left: 90vw;
transform: translate(-120%) scale(0.8, 0.8);
z-index: 2;
opacity: 0.7;
}
.single-slide:nth-child(2) {
order: 3;
}
.single-slide:nth-child(1) {
order: 2;
}
.single-slide:nth-child(3) {
order: 1;
}
<div class="all-slides">
<div class="single-slide" data-order="2">
<h3>First slide </h3>
<p>Some text</p>
</div>
<div class="single-slide" data-order="3">
<h3>Second slide</h3>
<p>Some other text</p>
</div>
<div class="single-slide" data-order="1">
<h3>Third slide</h3>
<p>Yet some other text</p>
</div>
</div>
This could be useful if you want to animate a slider (or anything else), but want to keep the order of the elements in the HTML for accessibility purposes, which is one of the useful usage of the order property. See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items#The_order_property_and_accessibility
Sadly no: the order attribute is animatable, but only as integers. That means that for each step/frame of the animation it will interpolate the value by flooring to the neareast integer. So items will only ever show up in the slot that the computed integer value results in, never in-between in any smooth sort of motion way.
It's technically still an animation: the calculated integer position should still follow the timing function and keyframe rules of the animation, it's just that the items "jump" from position to position as they change.
See https://developer.mozilla.org/en-US/docs/Web/CSS/integer#Interpolation
This question is old now, but I recently tested this, using this Fiddle (adapted from the one posted by Jason in a comment): http://jsfiddle.net/aqrxcd1u/ (code below).
In both Chrome and Firefox this partially animates, in that the order transitions one at a time from the current value to the target value. Meaning it doesn't go from 5->1 but instead goes 5->4->3->2->1.
In desktop Safari it still goes 5->1 directly.
#container {
display: flex;
}
#container div {
width: 100px;
height: 100px;
margin-right: 10px;
background-color: red;
}
#container div:nth-child(even) {
background-color: blue;
}
}
#container div:last-child {
order: 5;
transition: order 1s;
}
#container:hover div:last-child {
order: -1 !important;
}
<div id="container">
<div style="order: 1">Element 1A</div>
<div style="order: 2">Element 2B</div>
<div style="order: 3">Element 3C</div>
<div style="order: 4">Element 4D</div>
<div style="order: 5">Element 5E</div>
</div>
As Emil stated, it only animates as an integer. However, I am thinking of a hack:
Put the element you want to display in a wrapper with 1/10 of the height, set the wrapper overflow: visible
Then put 9 spacing element between these wrappers with the same height.
Put order and transition on all of them.
Change order of a wrapper and watch it 'transitioning'.
Sadly, it's ugly and only work in Firefox.
Here is what I tested in Angular
Related
I have a grid that draws squares in cells. It has number of rows and number of columns, then it draw the grid cells and check if in each cell there should be a square or not (according to an array) and draws a square if needed.
The HTML end result looks something like this: (lets say I have 1 row and 3 columns and only 2 cells should have squars)
.row {
display: flex;
flex-wrap: wrap;
flex: 10000 1 0%;
}
.column {
display: flex;
flex-wrap: wrap;
max-width: 100px;
min-width: 10px;
padding: 4px;
border: 1px solid grey;
}
.square {
background-color: red;
width: 100%;
aspect-ratio: 1/1;
border-radius: 5px;
}
<div class="row">
<div class="column">
<div class="square"></div>
</div>
<div class="column">
<div class="square"></div>
</div>
<div class="column"></div>
</div>
The rows take the full width of the screen and the size of the columns should be identical between all of the columns and changing by the number of columns on the screen (For example if I have 5 columns they should all be with a width of 100px, but if I have 1000 columns they should all be with a width of 10px).
My problem is that after a certain break point in the column size the padding and border radius seems weird and I want to change their values when I hit that break point.
I can't use #container queries as there are still not fully supported.
If it help I'm using vue 2. but I think a CSS solution will be better in this case.
Trying to address the issue described:
My problem is that after a certain break point in the column size the
padding and border radius seems weird and I want to change their
values when I hit that break point. I can't use #container queries as
there are still not fully supported.
I crafted a little demo that helped me better explore the conditions bringing to such a scenario.
Obtaining border: collapse equivalent on flexbox items
The .row element remains a flexbox container but its flex items instead of having their border set, they are styled with their outline set.
The outline doesn't occupy space and it's expected to "collapse" when colliding with the outline produced by another element.
So to make it sure the layout wasn't affected by styling oddities, in the attempt to show off the borders of the flex items, this demo just relies on 2 key aspects to render those borders:
Setting the gap between the flex items
Setting the outline size expected to cover the gap left between
elements
.row {
gap: var(--col-gap);
}
.column {
outline: var(--col-gap) solid gray;
}
Using ::after for adding content to an element
Plus the red dot is applied as an ::after pseudo element with position:absolute, again to make sure that nothing affected the grid layout:
.column.square::after {
position: absolute;
content: '';
background-color: red;
width: 50%;
aspect-ratio: 1/1;
border-radius: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
The dashboard - exploring the options
Starting from there I added a "dashboard" with position: fixed that remains on top of the page and lets you control:
column width (px): here you set the width changing the cols per row according to the available container space
columns per row: here you set the cols per row changing their width according to the available container space
width
gap between cells (px): the gap between cells on the grid
toggle red dots visibility: will show/hide the red dots proving again that display: none; doesn't change the grid layout that it's depending exclusively by the .column element size set through the custom variable --col-width
toggle counter visibility: will show/hide the counter on top of each flex item
Conclusions so far:
Despite the efforts to minimize any interfence and taking all the steps needed to correctly setup a grid layout depending only on the fixed size of its cells, there are still some rendering issue with sometimes the occurrence of regular mismatching patterns on the border size for some lines. I should say that I only experience the problem on my laptop display and not on my desktop monitor so that's another factor.
I tried with different parameters on my demo and playing with the numbers, considering also the gap. A good and safe layout can be found minimizing potential problems (also raising the border size for example).
I couldn't get further than this using the flex layout.
const container = document.getElementById('container');
//draws the board
emptyElementAndFillWithColumns(container, 100);
//sets some columns randomly as .square
addRandomSquares(container);
//initializes the dashboard with the value coming from the css custom props
let columnsGap = parseInt(getCssCustomProp('col-gap'));
let columnsWidth = parseInt(getCssCustomProp('col-width'));
document.getElementById('gap').value = columnsGap;
document.getElementById('width').value = columnsWidth;
document.getElementById('width').dispatchEvent(new Event('change'));
document.getElementById('cols').value = Math.trunc(container.offsetWidth / (columnsWidth+columnsGap));
//input#width change event handler
document.getElementById('width')
.addEventListener('change', event => {
const width = parseInt(event.target.value);
const newCols = Math.trunc(container.offsetWidth / (width+columnsGap));
setCssCustomProp(container, 'col-width', `${width}px`);
document.getElementById('cols').value = newCols;
});
//input#cols change event handler
document.getElementById('cols')
.addEventListener('change', event => {
const cols = parseInt(event.target.value);
const newWidth = Math.trunc(container.offsetWidth / cols) - columnsGap;
setCssCustomProp(container, 'col-width', `${newWidth}px`);
document.getElementById('width').value = newWidth;
});
//input#gap change event handler
document.getElementById('gap')
.addEventListener('change', event => {
const gap = parseInt(event.target.value);
setCssCustomProp(container, 'col-gap', `${gap}px`);
columnsGap = gap;
});
//input#toggle-dots change event handler
document.getElementById('toggle-dots')
.addEventListener('change', event => {
container.classList.toggle('hide-dots');
});
//input#toggle-counters change event handler
document.getElementById('toggle-counters')
.addEventListener('change', event => {
container.classList.toggle('hide-counters');
});
//sets the --propName custom property at the style of target
function setCssCustomProp(target, propName, value){
target.style.setProperty(`--${propName}`, `${value}`);
}
//gets the --propName custom property value from the rule set on :root
function getCssCustomProp(propName){
const propValue =
getComputedStyle(document.documentElement).getPropertyValue(`--${propName}`);
return propValue;
}
//resets the container and appends a count number of columns
function emptyElementAndFillWithColumns(target, count){
for (i = 0; i <= count; i++) {
const column = document.createElement('div');
column.classList.add('column');
target.append(column);
}
}
//adds the square class to random .column elements in target
function addRandomSquares(target){
target.querySelectorAll('.column').forEach(column => {
if (Math.random() >= 0.5)
column.classList.add('square');
})
}
:root {
--col-width: 100px;
--col-gap: 1px;
}
*,
*::after,
*::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
}
.row {
display: flex;
flex-wrap: wrap;
gap: var(--col-gap);
counter-reset: itemnr;
}
.column {
position: relative;
display: flex;
flex-wrap: wrap;
width: var(--col-width);
height: var(--col-width);
padding: 4px;
outline: var(--col-gap) solid gray;
}
.column.square::after {
position: absolute;
content: '';
background-color: red;
width: 50%;
aspect-ratio: 1/1;
border-radius: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.dashboard {
position: fixed;
right: 1rem;
top: 2rem;
border: solid darkgray;
padding: 1em;
z-index: 100;
background: gray;
color: white;
font-weight: 600;
font-size: 1.2rem;
opacity: .9;
}
.dashboard > *{
display: grid;
grid-template-columns: 1fr auto;
width: 100%;
gap: 1em;
}
.dashboard label{
}
.dashboard input[type="number"] {
width: 5em;
cursor: pointer;
}
.dashboard input[type="checkbox"] {
width: 1rem;
line-height: 1rem;
cursor: pointer;
}
#container.hide-dots .square::after{
display: none;
}
#container.hide-counters .column::before{
display: none;
}
small{
grid-column: 1 / -1;
font-size:.8rem;
text-align: center;
width: 100%;
margin-bottom: 1rem;
}
.column::before{
position: absolute;
counter-increment: itemnr;
content: counter(itemnr);
font-size: .8rem;
z-index: 10;
font-weight: 600;
}
<div id="container" class="row">
<div class="column square">
</div>
<div class="column"></div>
</div>
<div class="dashboard">
<div>
<label for="width">column width (px):</label>
<input
id="width" type="number" max="100" min="10">
</div>
<div>
<label for="cols">columns per row:</label>
<input
id="cols" type="number" max="50" min="1">
</div>
<div>
<label for="gap">gap between cells (px):</label>
<input
id="gap" type="number" max="10" min="0">
</div>
<div style="margin-top: 1rem;">
<label for="toggle-dots">toggle red dots visibility:</label>
<input id="toggle-dots" type="checkbox" checked>
</div>
<div>
<label for="toggle-counters">toggle counter visibility:</label>
<input id="toggle-counters" type="checkbox" checked>
</div>
</div>
If you want to increase or decrease size of padding you can give padding size in percent (%) that depends on parent element.
I'm having issues trying to make an image {image 16} appear when hovering on another image with a link {image 15}. I'm building my website on Cargo, as I don't know much about coding. This site-builder is limiting my options to only place my images in grids, and as these grids already have a div class each, i can't get Cargo to accept the div classes im making for each image- probably because i'm doing it wrong. To top it all off, Cargo doesn't allow me to see all of the HTML, just specific parts. Any help would be super welcomed, thank you in advance!
HTML:
<div class="image-gallery" gid="21">
<div class="hover-image">{image 16}</div>
</div> <br>
<br><br>
<br>
<div grid-row="" grid-pad="0" grid-gutter="0" grid-responsive="">
<div grid-col="x12" grid-pad="0"></div>
</div><br>
<div grid-row="" grid-pad="0" grid-gutter="0" grid-responsive="">
<div grid-col="x12" grid-pad="0"><div style="text-align: left"><div class="image-gallery" gid="22">
<div class="hover-title">
<a rel="history" href="enlightened-type" class="image-link">{image 15}</a>
</div>
{image 5}
{image 13}
{image 14}
{image 12}
{image 11}
</div><br></div>
</div>
</div>
<br><div class="image-gallery" gid="23">
{image 17}
</div>2
CSS:
.hover-title {
display: inline;
pointer-events: auto;
cursor: pointer;
}
.hover-image {
visibility: hidden;
}
body:not(.mobile) .hover-title:hover + .hover-image {
visibility: visible;
pointer-events: none;
}
.hover-image {
display: flex;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
pointer-events: none;
flex-direction: column;
align-items: center;
justify-content: center;
/* Change width and height to scale images */
width: 90vw;
height: 90vh;
}
.hover-image img {
max-width: 100% !important;
max-height: 100% !important;
width: auto !important;
height: auto !important;
margin-bottom: 0;
}
You're going to need some javascript.
Add an id to image16
<div class="hover-image" id="myhoverimage">{image 16}</div>
Add some event listeners to image15
<a rel="history" href="enlightened-type" onmouseover="showImg()" onmouseout="hideImg()" class="image-link">{image 15}</a>
The Javascript. (Put at the bottom of your html)
<script>
var hoverImg = document.getElementById("myhoverimage");
function showImg(x) {
hoverImg.style.visibility = "visible";
}
function hideImg(x) {
hoverImg.style.visibility = "hidden";
}
</script>
You can do the document. calls inline. 👍
<img src="./icon-off.png" name="MyImage">
I'm trying to make a progress bar with the CSS attr function, here is my code :
.progress_bar {
height: 10px;
width: 10cm;
background-color: #cae3ff;
}
.progress {
height: inherit;
width: attr(progress);
background-color: #0077ff;
transition: 1s cubic-bezier(1,0,0,1);
}
<div class="progress_bar">
<div class="progress" progress="80%"></div>
</div>
As you can see by running the code snippet, the bar doesn't work, I read the docs about the attr function and still I don't find the problem, could you help me please ?
attr() was originally intended for use with the content property and support for it there is good.
CSS Values and Units Module Level 4 adds support for any property (such as width), but that specification is currently a Working Draft and browser support is non-existent.
Try using the width rule in the style attribute like style="width:50%"
I found another way :
.progress_bar {
height: 10px;
width: 10cm;
background-color: #cae3ff;
}
.progress {
height: inherit;
background-color: #0077ff;
transition: 1s cubic-bezier(1,0,0,1);
}
<div class="progress_bar">
<div class="progress" style="width:80%"></div>
</div>
Using the style attribute, instead of creating a new attribute.
Try this one.
.progress_bar {
height: 10px;
width: 10cm;
background-color: #cae3ff;
}
.progress {
height: inherit;
width: calc(var(--width, 0) * 1%);
background-color: #0077ff;
transition: 1s cubic-bezier(1,0,0,1);
}
<div class="progress_bar">
<div class="progress" style="--width: 80;"></div>
</div>
I am trying to achieve what should be a fairly simple CSS transition. I have an address with phone number below. When the phone number is hovered, I want it to slide and fade off to the left, and text ("Call Us") to slide in to take it's place. I have achieved this as shown below, but it is quite glitchy and it's as if you need to hover very precisely for it to work properly. Why is this? Also the phone number isn't centred with the rest of the address (but "Call Us" is when hovered). Thanks for any help.
HTML:
<div class="address">
<p><b>ASCO TRANSPORT & LOGISTICS</b><br>
15 Ashby Close, Forrestfield<br>
Western Australia, 6058<br></p>
<p><span id="address1-phone">+61 (0)8 6254 7400</span><span id="address1-phone-text">Call Us</span></p>
</div>
CSS:
#address1-phone, #address1-phone-text {
transition: 0.2s;
cursor: pointer;
color: #009ee3;
}
#address1-phone:hover {
margin-left: -200px;
opacity: 0;
}
#address1-phone-text {
margin-right: -200px;
opacity: 0;
}
#address1-phone:hover + #address1-phone-text {
margin-right: 0px;
opacity: 1;
}
As the previous answers suggest, the <span> element that transitions to the left releases from the scope of :hover once it is no longer under the pointer- causing erratic behavior. My solution involves wrapping the ids in another <span> which gives you a static field to have attributed to the :hover selector.
This might be a redundant answer- but it's different in terms of more succinct organization & it produces your desired result.
#phone,
#address1-phone,
#address1-phone-text {
transition: 0.2s;
cursor: pointer;
color: #009ee3;
}
#address1-phone {
position: relative; /******/
}
#phone:hover #address1-phone {
margin-left: -200px;
opacity: 0;
}
#address1-phone-text {
position: absolute; /******/
margin-left: 200px;
opacity: 0;
}
#phone:hover #address1-phone-text {
margin-left: 0;
opacity: 1;
}
<center><div class="address">
<p>
<b>ASCO TRANSPORT & LOGISTICS</b>
<br> 15 Ashby Close, Forrestfield
<br> Western Australia, 6058
<br>
</p>
<p>
<span id="phone">
<span id="address1-phone">+61 (0)8 6254 7400</span>
<span id="address1-phone-text">Call Us</span>
</span>
</p>
</div></center>
Your problem is when you hover and it starts to move,it will go out of under of your mouse. So css hover can not solve your problem.
Use jquery instead:
$('#address1-phone').mouseenter(function(){
$(this).css('margin-right', '-200px');
$(this).next().css('margin-right', '0');
});
$('.address p').mouseleave(function(){
$(this).children().eq(0).css('margin-right', '0');
});
Or if you want to do with css only:
.address p:hover #address1-phone {
margin-left: -200px;
}
I'm working on this banner ad that I posted here yesterday and I got my images fading properly, but I had everything positioned in an absolute manner, and I need to have it so that when my ad expands, it pushes whatever content below it down. Right now, when I press expand, it covers the image below it, rather than push it down even though the picture's positioning is relative.
Here's a link to my project on codepen.
And here's my CSS:
#banner{
position: relative;
min-height: 100px;
}
.hide {
transition: opacity .5s ease-in-out;
opacity: 0;
position:absolute;
}
.show {
transition: opacity .5s ease-in-out;
opacity: 1;
position: absolute;
z-index: 1;
}
#toggle, #toggle2{
cursor: pointer;
}
#toggle{
margin-left:-123px;
}
#toggle2{
position: relative;
}
#twitterIcon{
position: relative;
}
.videoDiv > video {
display:inline-block;
border: 1px solid;
font-size:0;
margin: 0;
padding:0;
}
.videoDiv{
font-size:0;
margin-left:413px;
padding-top:152px;
}
I've read that absolute positioning makes it this way, but I need the collapsed and expanded version to be absolute so that they're on top of one another. Is there anyway I can make it so that the Coach ad pushes the image of Ron Swanson down rather than covering it?
Here is a complete solution: http://codepen.io/anon/pen/mewMEO
The solution is to make the smaller banner absolute with a negative z-index so it is in fact behind the normally positioned large banner.
Also, I took the liberty of improving your JS code by making it more generic and adding support for multiple banners on the page.
HTML
<div class="banner collapsed">
<img class="preview-size" src="http://i.imgur.com/y6foj3Z.jpg"/>
<img class="full-size" src="http://i.imgur.com/CeUfSAX.jpg"/>
<div class="btn-expand">
<img id="toggle" src="http://i.imgur.com/axmdldH.png" />
</div>
<div class="btn-collapse">
<img src="http://i.imgur.com/5wZwdGz.png" />
<a href="https://twitter.com/intent/tweet?text=I%20LOVE%20the%20new%20%40coach%20swagger!">
<img id="twitterIcon" src="http://i.imgur.com/WxSsDpb.png" />
</a>
</div>
</div>
<div class="push">
<img src="http://i.imgur.com/sFNERNs.jpg" />
</div>
CSS
.banner {
position: relative;
width: 970px;
}
.banner img {
/* Get rid of that margin on the bottom of the images */
display: block;
}
.banner .btn-collapse img {
display: inline;
}
.banner .btn-expand {
position: absolute;
bottom: 0;
right: 0;
}
.banner .preview-size {
z-index: -1;
position: absolute;
}
.banner .btn-expand {
display: none;
}
.banner.collapsed .preview-size {
z-index: 0;
position: relative;
}
.banner.collapsed .preview-size,
.banner.collapsed .btn-expand {
display: block;
}
.banner.collapsed .full-size,
.banner.collapsed .btn-collapse {
display: none;
}
JS
(function() {
var bannerEls = document.getElementsByClassName('banner');
// Support multiple banners
for (var index = 0; index < bannerEls.length; index++) {
var currBannerEl = bannerEls[index];
var expandEl = currBannerEl.getElementsByClassName('btn-expand')[0];
var collapseEl = currBannerEl.getElementsByClassName('btn-collapse')[0];
registerBannerToggle(expandEl, currBannerEl);
registerBannerToggle(collapseEl, currBannerEl);
}
function registerBannerToggle(clickableEl, bannerEl) {
clickableEl.addEventListener('click', function(e) {
toggleCollapseState(bannerEl);
});
}
function toggleCollapseState(bannerEl) {
if (bannerEl.className.indexOf('collapsed') !== -1) {
bannerEl.className =
bannerEl.className.replace(/collapsed/g, '');
}
else {
bannerEl.className += ' collapsed';
}
}
})();
The reason you are not able to do this was intentional to deter advertisers from messing with the actual website content. To pull it off, you would have to keep the position relative for the add or manipulate the ".push" div using javascript.
I dont know much plain javascript so I changed it for jQuery if you don't mind
All I've done was get images height and set animate on them with click on #toggle/#toggle2
CODEPEN