I don't often find myself with CSS issues, but this has me scratching my head.
In this plunk http://plnkr.co/i2Fxol, everything works exactly as expected in all browsers, with the exception of IE10 and 11 and Edge completely failing to apply css transitions to some elements, not all. And no matter how much I look at it, I cannot see why it's failing in those particular places. (CSS added here but it needs to be seen in context as this is a really simplistic view)
#navigation ul {
display: block;
list-style: none;
overflow: hidden;
transition: all .2s ease;
}
Essentially, they burp when the accordion is open and the menus are available to expand and contract in the 0.2 seconds stated. All the other transitions work.
Oh, and there is now way that I am ever going to add closures to svg paths within HTML5 just to remove those utterly pointless warnings in IE and Edge.
edit
The initial heights for the accordions are gathered on load with this:
...
var initialHeights = document.getElementsByClassName("level-1");
var values = [];
for(var i = 0; initialHeights.length > i; i++){
values.push(initialHeights[i].offsetHeight);
}
...
They are then zeroed inline with the values stored safely in an array using:
function toZero(){
for(i = 0; mainSubMenus.length > i; i++){
mainSubMenus[i].setAttribute("style", "height:0;");
}
}
toZero();
This allows the use of css to transition between a zero height and a known value.
brainfart
I thought that maybe the following would cure the ill:
mainSubMenus[i].style.height = 0;
I was wrong.
I believe the issue here is with the lack of an initial height. If you cycle over the elements, and set their heights equal to that of their computed height style, you could then use your collapse style to override the height to 0px, triggering a transition between two explicit numbers.
var box = document.querySelector( ".box" );
// Set the box's initial height to its computed height
box.style.height = window.getComputedStyle( box ).height;
// Hide the box
box.classList.add( "collapsed" );
// Click will toggle a "collapsed" class on the box
document.addEventListener( "click", e => box.classList.toggle( "collapsed" ) )
.box {
width: 100px;
overflow: hidden;
border: 1px solid #000;
transition: all .2s ease;
}
.collapsed {
height: 0!important;
}
<ul class="box">
<li>Hello</li>
<li>World</li>
</ul>
Related
Learning website development (HTML, CSS, JS, PHP, yadda yadda). I am trying to create a hover effect on an element via CSS, however after a JS function has set a value, it seems I cannot override without using the !important.
I have a scroll function in JS that sets opacity/color/etc. after the user has scrolled down. As it seems that JS affect the elements inline style directly, it will always have a specificity higher than anything in my stylesheet. As such, my :hover effect (increasing the opacity of the button) will be overwritten.
window.onscroll = function() {arcScrollEffect()}; // calls scroll function on scrolling
function arcScrollEffect() {
btn = document.getElementsByClassName("arcHeaderBtn")[0];
if (document.body.scrollTop > 50 || document.documentElement.scrollTop) {
btn.style.width = '20%';
btn.style.fontSize = '2em';
btn.style.backgroundColor = '#DDD';
btn.style.color = 'rgba(0, 0, 0, 1)';
btn.style.opacity = '0.3';
} else {
btn.innerHTML = 'RED';
}
}
.arcHeaderBtn {
background-color: inherit;
border: none;
position: fixed;
top: 10vh;
left: 0;
width: 100%;
font-size: 6em;
text-align: center;
opacity: 0.5;
border-radius: 0 0.6em 0.6em 0;
transition: color 1s, background-color 1s, width 1s, top 1s, font-size 1s, opacity 1s;
}
button.arcHeaderBtn:hover {
opacity: 0.5 !important;
}
<button class="arcHeaderBtn">Button</button>
Note that in this particular case I do have a workarounds, such as simply not setting opacity in JS or using the !important in CSS (which works fine). However, I've been told that !important is not good coding, something to the equivalent of a GOTO in older languages and I fear velociraptor attacks. Any suggestions?
I wouldn't recommend apply styles directly from JavaScript because as you could see, the inline styles will always have priority, unless you use the !important flag.
As a workaround, you could use JavaScript to add a CSS class to your element using element.classList.add, then you can have way more control in your styles.
I have this element that starts hidden and then gets animated with a css transition on a click event.
I know the display property cannot be animated, so what I do is remove the class that applies the display:none, and then make the change that triggers the css transition, like so:
popin.classList.remove('hidden') // removes the display:none property
setTimeout(() => {
popin.classList.remove('closed') // triggers transition
}, 10)
See this fiddle: http://jsfiddle.net/wre2674p/6/ for a full example.
I've found out that in order to work, the 2nd step must be done asynchronously. Put it in a setTimeout and it works...sort of. In Chrome, any timeout duration works (even 0).
For Firefox and Edge, the behavior varies. For 100ms, it works every time. But for a timeout of e.g. 10ms, the transition works only maybe 50% of times. Since it delays the animation, I wish to keep it as low as possible, while ensuring it works consistently.
I suspect it is related to reflow/repaint occurring when changing display property from none to block, but I lack details on these subjects to full understand what's happening and how to prevent it. Any idea?
Remove the hidden class from CSS and HTML, remove timeout from js. There is no need to display none the #popin since you already have overflow hidden. The transition can be triggered directly, you are over complicating things
document.getElementById('toggle').addEventListener('click', function(e){
let source = e.currentTarget
source.disabled = true
let popin = document.getElementById('popin')
if (popin.classList.contains('closed')){
popin.classList.remove('closed')
}
else{
popin.classList.add('closed')
}
setTimeout(() => {
source.disabled = false
}, 850)
})
body{
overflow: hidden;
}
#popin{
display: block;
position: absolute;
top: 0;
right: 0;
width: 400px;
height: 100vh;
/*transform: translate(0, 0);*/
transition: opacity 800ms;
opacity: 1;
background: lightgreen;
}
#popin.closed{
opacity: 0;
z-index: -1;
pointer-events: none;
}
<button id="toggle">toggle</button>
<div id="popin" class="closed">
<h1>Popin</h1>
</div>
I was reading this article http://semisignal.com/?p=5298 and the author wrote that
"Reflow needs to be triggered before the invisible class is removed in order for the transition to work as expected. "
My questions are :
1) Why does reflow need to be triggered?
2) I understand that we should avoid using reflow, if that is true why is the author suggesting to use reflow in order to make the transition work?
3) Instead of using reflow, is there a different method to make the transition work?
Thank you.
(Effectively: "Why can't I easily use transitions with the display property")
Short Answer:
CSS Transitions rely on starting or static properties of an element. When an element is set to display: none; the document (DOM) is rendered as though the element doesn't exist. This means when it's set to display: block; - There are no starting values for it to transition.
Longer Answer:
Reflow needs to be triggered because elements set to display: none; are not drawn in the document yet. This prevents transitions from having a starting value/initial state. Setting an element to display: none; makes the document render as if the element isn't there at all.
He suggest reflowing because it's generally accepted to hide and show elements with display: none; and display: block; - typically after the element has been requested by an action (tab or button click, callback function, timeout function, etc.). Transitions are a huge bonus to UX, so reflowing is a relatively simple way to allow these transitions to occur. It doesn't have an enormous impact when you use simple transitions on simple sites, so for general purposes you can trigger a reflow, even if technically you shouldn't. Think of the guy's example like using unminified JavaScript files in a production site. Can you? Sure! Should you? Probably not, but for most cases, it won't make a hugely noticeable difference.
There are different options available that prevent reflowing, or are generally easier to use than the method in the link you provided. Take the following snippet for a few examples:
A: This element is set to height: 0; and overflow: hidden;. When shown, it's set to height: auto;. We apply the animation to only the opacity. This gives us a similar effect, but we can transition it without a reflow because it's already rendered in the document and gives the transitions initial values to work with.
B: This element is the same as A, but sets the height to a defined size.
A and B work well enough for fading in elements, but because we set the height from auto/100px to 0 instantly, they appear to collapse on "fade out"
C: This element is hidden and we attempt to transition the child. You can see that this doesn't work either and requires a reflow to be triggered.
D: This element is hidden and we animate the child. Since the animation keyframes give a defined starting and ending value, this works much better. However note that the black box snaps into view because it's still attached to the parent.
E: This works similarly to D but we run everything off the child, which doesn't solve our "black box" issue we had with D.
F: This is probably the best of both worlds solution. We move the styling off the parent onto the child. We can trigger the animation off of the parent, and we can control the display property of the child and animate the transition as we want. The downside to this being you need use animation keyframes instead of transitions.
G: While I don't know if this triggers a reflow inside the function as I haven't parsed it myself, you can just simply use jQuery's .fadeToggle() function to accomplish all of this with a single line of JavaScript, and is used so often (or similar JS/jQuery fadeIn/fadeOut methods) that the subject of reflowing doesn't come up all that often.
Examples:
Here's a CodePen: https://codepen.io/xhynk/pen/gerPKq
Here's a Snippet:
jQuery(document).ready(function($){
$('button:not(#g)').click(function(){
$(this).next('div').toggleClass('show');
});
$('#g').click(function(){
$(this).next('div').stop().fadeToggle(2000);
});
});
* { box-sizing: border-box; }
button {
text-align: center;
width: 400px;
}
div {
margin-top: 20px;
background: #000;
color: #fff;
}
.a,
.b {
overflow: hidden;
height: 0;
opacity: 0;
transition: opacity 3s;
}
.a.show {
height: auto;
opacity: 1;
}
.b.show {
height: 100px;
opacity: 1;
}
.c,
.d {
display: none;
}
.c.show,
.d.show {
display: block;
}
.c div {
opacity: 0;
transition: 3s all;
}
.c.show div {
opacity: 1;
}
.d div {
opacity: 0;
}
.d.show div {
animation: fade 3s;
}
#keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
.e div {
display: none;
}
.e.show div {
display: block;
animation: fade 3s;
}
.f {
background: transparent;
}
.f div {
background: #000;
display: none;
}
.f.show div {
display: block;
animation: fade 3s;
}
.g {
display: none;
}
<button id="a">A: Box Height: Auto</button>
<div class="a">This<br/>Has<br/>Some Strange<br/><br/>Content<br>But<br>That doesn't really<br>Matter<br/>Because shown,<br/>I'll be<br/>AUTO</div>
<button id="b">B: Box Height: 100px</button>
<div class="b">Content For 2</div>
<button id="c">C: Hidden - Child Transitions (bad)</button>
<div class="c"><div>Content<br/>For<br/>3<br/></div></div>
<div style="clear: both;"></div>
<button id="d">D: Hidden - Child Animates (Better)</button>
<div class="d"><div>Content<br/>For<br/>4<br/></div></div>
<div style="clear: both;"></div>
<button id="e">E: Hidden - Child Hidden & Animates</button>
<div class="e"><div>Content<br/>For<br/>5<br/></div></div>
<button id="f">F: Child Has BG & Animates (Works)</button>
<div class="f"><div>Content<br/>For<br/>5<br/></div></div>
<div style="clear: both;"></div>
<button id="g">G: This uses fadeToggle to avoid this</button>
<div class="g">I animate with<br/>JavaScript</div>
<footer>I'm just the footer to show the bottom of the document.</footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I created this demo:
http://cristiantraina.altervista.org/boxfall/
When you click, it creates a red falling box.
The problem is that using only css there are no ways to detect the size of the screen, in fact in my demo I specify that the box has to fall for 1000px, regardless of the actual height of the screen.
This is the code of the keyframe:
#include keyframes("fall"){
to{
top: 1000px;
}
}
I can't use bottom:0px; because I wouldn't know from where to start the fall, and I didn't solve my main problem.
This is the FallBox.js script:
function FallBox(x, side, parent){
this.x = x;
this.parent = parent || $("body");
this.side = side || Math.random()*200;
this.createBox();
this.fall();
}
FallBox.prototype.createBox = function(){
box = document.createElement('div');
$box = $(box); // I hate brackets
$box.addClass("box");
$box.css({
width: this.side+"px",
height: this.side+"px",
left: this.x+"px",
top: "-"+(this.side+5)+"px"
});
this.box = $box;
}
FallBox.prototype.fall = function(){
this.parent.append(this.box);
this.box.addClass("fall");
}
I know that I could use overflow:hidden; in the parent div, but I don't think that this is the ideal solution. First because a user can have got a screen with a superior height, then because I want to the box stops when it meets the edge, as the border was ground and it shouldn't pass through.
Another solution that I found on the web, it's to use the CSSOM API, but not even mozilla developers are sure of the compatibilty of these.
So, how can I stop an animation when it meets the screen edge, since javascript fails to inject properties?
Thank you.
If you're looking for a css-only solution, you could use the css calc feature (http://caniuse.com/#feat=calc) in combination with vh (http://caniuse.com/#search=vh).
document.querySelector(".box").addEventListener("click", function() {
this.classList.toggle("is-dropped");
})
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.box {
position: absolute;
top: 0;
left: 200px;
width: 100px;
height: 100px;
background: red;
transition: top 2s;
}
.box.is-dropped {
top: calc(100vh - 100px);
}
<div class="box"></div>
You coul use the translatey() CSS transform function to shift each div up by 100% of its own height. That way you would just need 2 rules to change the value of the top position without having to worry about height in each case.
(function(d,M){
var div=d.createElement("div"),
wait=0,size;
d.body.addEventListener("click",function(){
if(!wait){
wait=1;
div=div.cloneNode(1);
div.classList.remove("go");// necessary so that newly created divs don't just get added to the bottom of the page
size=M.max(M.floor(M.random()*200),50);
div.style.height=div.style.width=size+"px";
div.style.left=M.max(M.floor(M.random()*this.offsetWidth)-size,0)+"px";
this.appendChild(div);
setTimeout(function(){
div.classList.add("go");// adding this class starts the animation.
wait=0;
},5);
}
},0);
})(document,Math);
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%}
div{
background:#000;
border:1px solid #fff;
transition:top 2s linear;
position:absolute;
top:0;
transform:translatey(-100%);
}
div.go{
top:100%;
}
ORIGINAL SOLUTION
As the height of the box is being set dynamically in your JavaScript, your CSS isn't going to know the height of each box but that doesn't stop you using the CSS calc() function to set the top position you want to animate each to, much like you currently do to set its starting top position. Here's a quick, rough example, with an alternative solution in the comments that doesn't use calc(), if you'd prefer.
var div=document.createElement("div"),
wait=0,size;
document.body.addEventListener("click",function(){
if(!wait){
wait=1;
div=div.cloneNode(0);
size=Math.max(Math.floor(Math.random()*200),50);
div.style.height=div.style.width=size+"px";
div.style.left=Math.max(Math.floor(Math.random()*this.offsetWidth)-size,0)+"px";
div.style.top="-"+size+"px";
this.appendChild(div);
setTimeout(function(){
div.style.top="calc(100% - "+size+"px)"; /* This is the important bit */
// div.style.top=document.body.offsetHeight-size+"px"; /* Alternative solution, without using calc() */
wait=0;
},5);
}
},0);
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%}
div{
background:#000;
border:1px solid #fff;
transition:top 2s linear; /* Using a transition instead of an animation */
position:absolute;
}
I want to add bullet points between my flexbox navigation menu, but only have them appear between flex items on the same line. Not at the beginning or end of line, and not at all if the flex item is on the line by itself.
I've been using this (CSS styling for horizontal list with bullet only between elements) question as a starting point, but modified combined the top two answers to make it work with a flexbox.
Here's what I've got:
http://codepen.io/anon/pen/EjQzWZ
JS:
<script>
$(function() {
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top) {
$(lastElement).after( $('<li>').attr('class', 'bullet') );
}
lastElement = $(this);
});
});
</script>
CSS (essentially the exact same as the original question I linked to):
<style>
.bullet {
width: 6px;
height: 7px;
background-position: 50% 50%;
background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAOElEQVQI113M0Q3AIBRC0aM76P7jmHSmSj/6mibyc4EQkEEWuYtDmU1SXO1d6H7tjgupIl8+P+cD22cff9U1wbsAAAAASUVORK5CYII=);
}
</style>
Most of the time everything renders correctly. But at certain browser widths, bullet points are added where they shouldn't be. For instance, if the window is re-sized to a width of 678 px (according to the codepen.io counter that displays in the middle of screen while re-sizing), a bullet point appears on the right side of the top row when it shouldn't. (Depending on the browser it may work correctly at 678px, but there are several spots that it does occur, and I've only tested this on Chrome and IE11).
Sometimes a refresh is required to fix the bullet points locations, which is understandable, but at some widths refreshing doesn't help and they reappear incorrectly.
I believe the problem is caused by the extra space the bullet points add.
I think it would be a simpler and cleaner solution to do it entirely via CSS instead of JS, but I'm not sure that's possible with flexbox items. Even though the flexbox items appear to take up a wider width than their content, the css selector ::after seems to only calculate the actual content width.
What is going on with my JS? Can I make it better/cleaner?
You can put your bullet logic in a function, then call that function on page load and attach it to the window resize event.
The updated javascript would look like this:
$(function() {
addBullets();
$(window).resize(addBullets);
});
function addBullets(){
$('li.bullet').remove(); //remove bullets before you add new ones
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top) {
$(lastElement).after( $('<li>').attr('class', 'bullet') );
}
lastElement = $(this);
});
}
See this updated CodePen for a demo.
Also, I don't think this would be possible with pure CSS.
UPDATE
The reason the bullet was showing up on the edge in those certain widths is because the bullets them selves where <li>'s also and were taking up 6px a piece. So when you iterate through the <li>'s It would correctly identify the correct element to put the bullet after, but when the browser renders all the small bullets it would... at certain widths... shove the last <li> to the next line.
To correct this I have created a CSS class for the bullets and will add that to the items that need bullets instead of creating new <li>'s which would take up additional space.
New CSS:
.flex-item {
position:relative; /*added*/
flex: 1 1 auto;
color: #a6a7a9;
font-size:1.5em;
padding: 50px; /*use padding instead of margin for easier absolute positioning of bullet*/
color: black;
text-align: center;
}
.flex-item.bullet:after{
position:absolute;
right:0;
top:50%;
transform:translateY(-50%);
color:blue;
content:"•"; /*could also be "url(data:image/png;base64 ..."*/
}
New Javascript:
function addBullets(){
$('li.bullet').removeClass('bullet'); //remove all bullet classes
var lastElement = false;
$("ul li").each(function(index) {
if (lastElement && lastElement.offset().top == $(this).offset().top)
$(lastElement).addClass('bullet'); //add class
lastElement = $(this);
});
}
See this updated CodePen.
If for whatever reason you can't use this CSS class approach you could add your bullets (as <img> or <div>) inside the <li> and position them absolutely with CSS. As long as they don't contribute to the overall width of the <ul> it should work fine.
You can do this using CSS only.
Delete your .bullet definition, and add this CSS:
.flex-container {
margin-left: -10px; //hide the first bullet on each row
}
.flex-item {
position: relative; //so we can have absolute positioning on the :before
}
.flex-item:before {
content: "•"; //the bullet
position: absolute; //positioned absolutely
left: -55px; //account for the 50px margin and the bullet's width
}
Updated CodePen
div:before{
content:"\b7\a0"; /* \b7 is a middot, \a0 is a space */
font-size:30px;
}
Or try this
div:before{
content:"•";
}
You can use the :not(:first-child) selector to apply the bullet to all but the first child.
.flex-row
{
display: flex;
flex-direction: row;
&.bulleted
{
div:not(:first-child):before{
content: "•";
}
}
}