JavaScript media query vs CSS media query - javascript

I've noticed that the JavaScript media query seems to take effect after the CSS equivalent ones.
I've created two examples demonstrating what I'm talking about:
First example
HTML:
<div class="foo">
bar
</div>
CSS:
.foo {
background-color: orange;
}
#media(max-width: 300px) {
.foo {
background-color: blue;
transform: translateY(100px);
transition: all 300ms ease-out;
}
}
jsbin link is: here
Here transition happens, when screen width becomes 300px or less from something bigger.
But when creating responsive design such transition can be annoying. I'm trying to get rid of them. The following Javascript and CSS solves the problem, but I'm not sure that is it reliable or not.
2nd example
HTML
<div class="foo">
bar
</div>
<button>toggle translateY to 200px</button>
CSS
.transition {
transition: all 300ms ease-out;
}
.foo {
background-color: orange;
}
#media(max-width: 300px) {
.foo {
background-color: blue;
transform: translateY(100px);
}
}
.translateY {
transform: translateY(200px);
}
JavaScript:
const w = window.matchMedia("(max-width: 300px)");
const div = document.querySelector(".foo");
const button = document.querySelector('button');
function fun(e) {
if (e.matches) {
div.classList.add('transition');
} else {
div.classList.remove('transition');
}
}
// for initial screen width change detection
fun(w);
w.addEventListener('change', fun);
button.onclick = function() {
div.classList.toggle('translateY');
}
jsbin link is here
Here it seems the following thing happens in order when screen width becomes 300px or less from something bigger:
CSS transform: translateY(100px) is rendered in a flash.
transition class is added to div by JavaScript.
By clicking the button, it makes sure that the transition class is working.
This example doesn't cause any unwanted transition as screen size becomes 300px or less from something bigger.
So it seems that any CSS media query is rendered before JavaScript equivalent media queries. I think it's a good thing. But I'm not sure, is it the standard well supported behavior? Is it safe to build logic based on this behavior?

This is part of the CSS specificity
Inline styles added to an element (e.g., style="font-weight: bold;") always overwrite any styles in external stylesheets, and thus can be thought of as having the highest specificity.
The javascript code you posted will add inline styles (through the style property of the element) and thus has the highest specificity. (it has nothing to do with the js media query, it just has to do with how you apply the style in the JS to the element)
Update after the comments/update in question
Again it depends on when you load the CSS and the JS. If you first include the CSS file, since it is a render blocking resource, it will be applied first.
I am not sure though, why don't you apply all the rules through CSS media queries ?
const div = document.querySelector(".foo");
const button = document.querySelector('button');
button.onclick = function() {
div.classList.toggle('translateY');
}
.foo {
background-color: orange;
}
#media(max-width: 300px) {
.foo {
background-color: blue;
transform: translateY(100px);
transition: all 300ms ease-out;
}
}
.translateY {
transform: translateY(200px);
}
<div class="foo">
bar
</div>
<button>toggle translateY to 200px</button>

Related

Is there another way to add "hover" effect without !important after JS has set a value?

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.

Why does reflow need to be triggered for CSS transitions?

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>

Edit Media queries CSS with event listener

I have an unordered list <ul> that I would like to add an event listener too
if it fits a certain media query.
The original css :
#media (max-width: 414px) {
transition: all 3s ease-out;
height: 0px;
display: none;
}
the eventlistener function :
if(window.matchMedia("max-width: 414px")) {
console.log(list.style.height);
console.log(list.style.display);
list.style.height = 'auto'
list.style.display = 'unset'
}
HELP : the transition seems not to be working & the console log for list.style.height & list.style.display is both empty ''
The transition won't work because you are changing display from/to none. In addition, you need to transition the height between numeric values (auto doesn't count).
CSS:
#media (max-width: 414px) {
transition: all 3s ease-out;
height: 0;
}
JS:
if (window.matchMedia("(min-width: 414px)").matches) {
list.style.height = list.style.height + 'px'; // set the exact height
}
Its not working and your console is empty because of this line
if(window.matchMedia("max-width: 414px")) {
It must be
if (window.matchMedia("(max-width: 414px)").matches) {
And transition is not working with display:none and height:auto..
You can try opacity instead of display.
I made a sample fiddle..
And try to look at the console you can see that its not empty anymore.

How to stop a CSS animation when it meets the screen edge?

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;
}

Using CSS to target a style other than itself

It might only be possible with JavaScript, but I was wondering if it was possible to link one style to another with events like :focus or :hover in CSS alone.
For example, could the class "hitArea" change the "changeArea" background attribute when in focus?
.changeArea { background: yellow; }
.hitArea div:focus { changeArea:changeBG; }
.changeArea changeBG { background: blue; }
I know there is communication between styles when doing CSS animations, like in this working example:
.reveal {
position:absolute;
top:190px;
left:0px;
margin-left:0px;
-webkit-animation-name:reveal;
-webkit-animation-duration:0.1s;
-webkit-animation-fill-mode:backwards;
-webkit-animation-delay:0.2s;
animation-name:reveal;
animation-duration:0.1s;
animation-fill-mode:backwards;
animation-delay:0.2s;
}
#-webkit-keyframes reveal {
0% { left:-900px; -webkit-animation-timing-function:linear; }
99% { left:-900px; -webkit-animation-timing-function:linear; }
100% { left:0px; }
}
So what is the syntax, or is it even possible, for me to communicate between other styles?
If your HTML looks like this:
<div class="hitarea">
<div class="changeArea"></div>
</div>
Then you can target changeArea when hitArea is focused like this:
.hitarea:focus .changeArea {
background-color: red;
}
This will only work when "changeArea" is some child of hitarea.
Read more on CSS Selectors and what you can do with them here: https://developer.mozilla.org/en-US/docs/CSS/Getting_Started/Selectors
I don't believe it is possible. You can take a look at http://sass-lang.com/ which allows you to do things like that.
If .changeArea is a child of .hitArea, yes!
.hitArea:focus .changeArea{ /* Styles to apply when .hitarea is hovered */ }
If that's not what you want, I recommend setting it up so that when .hitArea is focused, javascript applies a class to .changeArea that has the style you want to apply.

Categories