Pushable button using CSS - html

I'm trying to create a slightly animated pushable button.
My goal is to achieve a button like this:
see the source code (jsfiddle)
with using exactly one HTML tag and the minimal amount of new CSS features like transform and transition.
However as you see in the jsfiddle; if you click the button at least twice the whole line (and the content beneath) bounces too.
That is ofcourse because of the margin set within the :active selector.
My second approach was using the the CSS transform property. That worked perfectly except I still had the need the change the height of the button (which will result in the same problem).
So my question is: How can I achieve the same effect without adjusting the positions of other elements?

How can I achieve the same effect without adjusting the positions of other elements?
I suggest using position: relative; for the button, then set top: 2px for :active pseudo-class, as follows:
button {
/* Other styles... */
position: relative; /* position the element as relative */
outline: none; /* Just added for the demo */
}
button:active {
top: 2px; /* move the element without affecting the others' position */
box-shadow: inset 0px 2px 1px 0px rgba(0, 0, 0, .1);
}
JSFiddle Demo.

You can use transform: translate property over top/left properties (Check here if you know why) and of course if you don't care about legacy IE browsers:
button {
....
transition: all .1s ease-in-out;
-moz-transition: all .1s ease-in-out;
-webkit-transition: all .1s ease-in-out;
....
}
button:active {
transform: translatey(2px);
-moz-transform: translatey(2px);
-webkit-transform: translatey(2px);
...
}
Demo

Related

Transition to default class upon removal of CSS class with animation

I am animating a button. A class is assigned depending on my app state. This is actually implemented in Svelte as follows:
<div class="default"
class:run-animation="{$animate === true}">
But the equivalent in vanilla javascript without Svelte is:
let element = document... (find element)
element.classList.add("run-animation")
... later ...
element.classList.remove("run-animation)
For the sake of a minimum reproducible example, the classes I'm trying to animate/transition between look like this:
.default {
top: 20px;
color: white;
}
#keyframes button-animation {
from {
top: 20px;
color: white;
}
20% {
top: 23px;
color: white;
}
25% {
color: red;
}
100% {
top: 23px;
color: red;
}
}
.run-animation {
animation-name: button-animation;
animation-duration: 2s;
/* Preserve the effect of the animation at ending */
animation-fill-mode: forwards;
}
I add the class to the element, and the button animates just like I want it to. My problem arises when I remove the class. I want the button to transition smoothly back to the default CSS. I have tried adding the animation to the run-animate class:
.run-animation {
animation-name: ... ;
top: 23px;
color: red;
}
I have come across many people stating the transition upon class removal will apply if I add a transition property to the default class. I have tried this as follows:
.default {
...
transition: all 3s linear;
}
But it isn't working. The animation runs smoothly when it is added but the styling immediately reverts to the default when the class is removed (no smooth transition).
MY GOAL: I want to smoothly transition away from the end-state of the animation to the default class when the animate class is removed. Is this possible?
Ideally, I'm adding the class with the Svelte logic at the top so the animation should not be triggered in javascript but rather naturally occur as a result of class assignment.
(My code in practice is a little more complicated than shown, the button has another class with styles not being animated at all and the animation includes more styles such as box-shadow and text-shadow. Still, I don't see why this should be more problematic than just color and top included above)
// JS only toggles '.animation'
document.querySelector("button").addEventListener("click", () => {
document.querySelector("div.default").classList.toggle("animation");
});
body {display: flex}
button {position: absolute; left: 120px}
div.default {
position: absolute;
width: 100px;
height: 100px;
background: darkgreen;
}
/* Above code to make a visible working example */
div.default {
top: 20px;
color: white;
transition: top 0.4s, color 0.1s 0.4s;
}
#keyframes define-animation {
from {
top: 20px;
color: white;
}
}
div.default.animation {
animation-name: define-animation;
animation-duration: 2s;
top: 24px;
color: red;
}
<div class="default">I'm colourful</div>
<button>Toggle ".animation"-class</button>
Above is a working snippet with an animation running on class addition and no reverse transition on class removal. I have tried setting animation direction to opposite values in .default and .animation. I have tried defining the .animate end state properties in the class and/or in the keyframes to attributes.
EDIT: It works now! How?
You cannot apply:
animation-fill-mode: forwards;
The end-attributes need to be defined in the animate class not in the keyframe.
The animation plays when the class is added. Transition timings are used when the class is removed (if the animation has completed).
To get a transition effect, you can use the transition-property.
The transition-property can be used here, since every property you want to animate only has a start- and end-value.
Translating animation-percentages to seconds
To translate the percentages of your CSS Animation button-animation to seconds, you just calculate 'percentage' * 'animation-duration'.
This works for both the transition-duration-property as well as for the transition-delay-property.
Example:
color is being animated from 20% to 25%, which is a duration of 5% with a delay of 20%.
All in all, the animation should take 2 seconds.
So we calculate for:
transition-duration: 5% * 2s = 0.1s
transition-delay: 20% * 2s = 0.4s
With that, we can add transition: color 0.1s 0.4s to the .default-class.
Why add it to .default, and not to .animation?
If we were to add the transition-property to .animation, the following would happen:
When adding .animation, there will be a transition-effect, since the element now has a transition-property defined.
But when removing .animation, the element would no longer have a transition-property defined, meaning there would be no transition.
Now, we want to transition on both adding and removing .animation, meaning we want to have a transition-property defined both when .animation is present and when it is not. That means, transition should not be defined in .animation.
// JS only toggles '.animation'
document.querySelector("button").addEventListener("click", () => {
document.querySelector("div.default").classList.toggle("animation");
});
body {display: flex}
button {align-self: center}
div.default {
position: relative;
border: 2px solid black;
width: 100px;
height: 100px;
background: darkgreen;
}
/* Above code to make a visible working example */
div.default {
top: 20px;
color: white;
transition: top 0.4s, color 0.1s 0.4s;
}
div.default.animation {
top: 23px;
color: red;
}
<div class="default">Some text to see the "color"-property</div>
<button>Toggle ".animation"-class</button>
Why does it behave differently...
...when placing the properties inside the to-section of the animation, than when placing them inside .animation itself?
That is, because the properties are not directly applied to the element itself, but rather the element is stopped in its animation (right at the very end), giving only the appearance of the properties being actually applied.
Removing animation-fill-mode: forwards shows the actually applied properties after the animation has played. Those actually applied properties will be the start-values for transition after .animation is removed.
When defining these properties in .animation, they will inherently be the to-values for the animation (if not defined otherwise in animation itself), and be the applied properties of the element.
That means, when removing .animation, the transition will start from there.

Transform scale() Safari bug when used on elements with border

CSS transform scale() function appears to have a bug on Safari when it's used on elements with a border.
I'm trying to zoom an image on mouse over using transform: scale() function but if the image has a border then it gets pixelated when scaled.
Here is a sample of the same element with the same CSS rules applied (except the border):
Code example: https://jsfiddle.net/m6g4kw30/
div {
text-align: center;
}
img {
height: 100px;
-webkit-transition: all .3s ease;
-moz-transition: all .3s ease;
-o-transition: all .3s ease;
-ms-transition: all .3s ease;
transition: all .3s ease;
border: 1px solid #000;
margin: 20px;
}
img.noborder {
border: none;
}
img:hover {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transform: translateZ(0) scale(5);
-moz-transform: scale(5);
-ms-transform: scale(5);
-o-transform: translateZ(0) scale(5);
transform: translateZ(0) scale(5);
}
<div>
<img src="https://via.placeholder.com/1000.png" alt="">
<img src="https://via.placeholder.com/1000.png" class="noborder" alt="">
</div>
CSS transform scale() function appears to have a bug on Safari when it's used on elements with a border.
You can say that again! Unfortunately, the reported bug(s) for this (and similar) issues go back many years, with the following bug referenced in most:
https://bugs.webkit.org/show_bug.cgi?id=27684 (Opened in 07/2009)
If you didn't catch the date, it's a 10 year old bug that's still causing developers issues today! YIKES.
Basically, the issue comes down to Safari rasterizing the layer. On transform/scale, it resizes the layer, however it does not re-render the rasterized layer. In your use-case, the rasterized image is scaled up, but the text/image is blurry.
As for a workaround/fix? There are a couple ways you can "address" this:
1) Force a re-render
A quick/easy fix is to force Safari to re-render your layer when you transform. One way this can be achieved is by applying a CSS property which you then change after transforming (some people have success changing a background-color, for example). For your specific use case, I had luck with the following combination:
img {
outline: 1px solid #000;
border: none;
}
img:hover {
outline: none;
border: 1px solid #000;
}
By toggling those specific values, I was able to force Safari to re-render the rasterized layer, thus rendering a sharp image (similar to the non-border example). Here's a JSFiddle with the full code example: https://jsfiddle.net/gc56brfh/
2) Scale down, then up
Another workaround, documented here, is to set the element's initial size to the "scaled up" dimensions, and then scale down the element until you're ready to scale it up. That way, the element is rasterized to the correct dimensions.
CSS wise, that may look like:
img {
-webkit-transform: translateZ(0) scale(0.2);
height: 250px;
}
img:hover {
-webkit-transform: translateZ(0) scale(1);
}
In the above, we've set the initial size of the img to 250px (this is based on your original css, with images being 50px and then scaled up 5). We then scale down the image by 0.2, resulting in 50px. On hover, we then scale back up to 250px by setting scale(1).
Here's an updated JSFiddle: https://jsfiddle.net/df2zqgnx/
One thing to note is that other CSS properties might need to be updated with this workaround. For example, you'll notice in the fiddle I also needed to update the border from 1px to 5px to compensate for the scaling down.
Anyway, hope this was helpful and one of the solutions works for you!

z-index does not work in div under overlay

This is an image depicting my problem
The grey overlay is on top of the body tree and has the following css:
overlay{
z-index: 500;
background: black;
width: 100%;
height: 100%;
opacity: 0.7;
position: absolute;
display: none;
}
The div beneath the overlay (with the top border) has the following css
inside_div{
height: 575px;
width: 50%;
float: left;
border: 2px solid green;
padding:20px;
overflow-y:scroll;
}
This class is added to .inside_div dinamically:
.inside_div(another_class_added_dinamically){
outline: none;
border-color: green;
box-shadow: 0 0 10px green;
opacity: 1;
-webkit-animation: glow 0.7s infinite alternate;
-webkit-transition: border 0.7s linear, box-shadow 0.7s linear;
-moz-transition: border 0.7s linear, box-shadow 0.7s linear;
transition: border 0.7s linear, box-shadow 0.7s linear;
}
And all elements inside the "inside_div" are added the following class dinamically: (notice the following one affects all children too with the '*')
.inside_div(another_class_added_dinamically), .inside_div(another_class_added_dinamically) *{
z-index: 1000 !important;
}
However, as seen in the image, not all elements stand out (the background inside the div remains grey). I would need to know a way of toggling the two previous classes dinamically to highlight the elements as described. What exactly is wrong here? Thank you very much for your help
From W3 Schools:
Note: z-index only works on positioned elements (position:absolute, position:relative, or position:fixed).
Very likely the elements you are giving the z-index do not have position on one of those three.
Hope that helps
You don't seem to set the position property on your inside_div element. You should post a fiddle with your code.
A bit late with this answer, but your problem could be caused by the fact that your overlay div has an opacity < 1. As a result, a new stacking context is being created, which might explain the behavior you see. It's hard to tell without seeing all of your code. Here is an excellent article explaining stacking context in more detail:
https://philipwalton.com/articles/what-no-one-told-you-about-z-index/

Jerky CSS3 transition in Firefox

I am trying to do a simple image fade on rollover - works fine and smooth in Chrome, but Firefox is a bit jumpy. I've tried doing the backface-visibility trick on the container, but still no luck.
Anyone have any ideas?
JSFiddle
HTML
<div class="link-box large">
<div class="image">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRStwH3maKRqLU8lLOo1XbO6uZIKHRyf2PGv66H6ol5mB0kS_0r" alt="">
</div>
</div>
CSS
.link-box .image img { transition: all .2s ease-out; width:200px; }
.link-box.large { position: relative;}
.link-box.large:hover .image img { opacity: .65; }
My best guess is that setting the width of the image to 200px and leaving the height unspecified is causing the browser to calculate the height of the image. If the height calculates to a nice whole number it isn't an issue. If the height calculates to a decimal it may be the cause of the problem.
In this case the natural dimensions of the image are 275px by 183px.
By changing the width of the image to 200px you are shrinking the image to 72.727272...% of its natural size.
275/200 = 0.727272... Or if you prefer fractions: 275(8/11) = 200
Now running the same equation on the height yields:
183(8/11) = 133.090909...
It looks like, under the normal run of things, the partial pixels are cropped, but during the transition the partial pixels aren't being cropped, and the image is warped slightly to show the partial pixels within the same height.
Cropped down to 133px:
Not cropped and slightly warped:
Now that we have a good hypothesis on what's causing the problem, on to the solutions:
You can hard code the height of the image:
Working Example
.link-box .image img {
transition: all .2s ease-out;
width:200px;
height: 133px; /* manually set the height */
}
Or if you would rather not hard code the height, you can also fix the issue with an anti-alias hack, just add a box-shadow.
Working Example
.link-box.large:hover .image img {
opacity: .65;
box-shadow: 0 0 0 #000; /* add a non-visible box-shadow */
}
Or if you're concerned about the cross-browser compatibility of using a box-shadow, you can also use a transparent border:
Working Example
.link-box .image img {
transition: all .2s ease-out;
width:200px;
border: 1px solid transparent; /* add transparent border */
}
Works good on my Firefox.
Anyway you can try to add some special attributes that will prepare the browser for the transition and actually render the element with possible transformation in mind.
Such an attribute is transform: translate3d(0,0,0);
Like this :
.link-box .image img {
transition: all .2s ease-out;
width:200px;
transform: translate3d(0,0,0);
}
.link-box.large { position: relative;}
.link-box.large:hover .image img { opacity: .65; }

Modify parent element style

My blog simulates a terminal screen, so normal text is green and links are in red with a red background when the mouse is over. Since I use a monospaced font throughout the blog, <code> is styled to have a green background to differentiate from regular text. Likewise, <code> inside <a> has a red background that turns darker when the mouse is over. See this test page for a live version.
Here is the CSS (complete file here) for <a> tags:
a {
color:#CD0000;
text-decoration:none;
transition: background-color .6s;
-webkit-transition: background-color .6s; /* Safari and Chrome */
-moz-transition: background-color .6s; /* Firefox 4 */
-o-transition: background-color .6s; /* Opera */
}
a:hover {
background-color:#440000;
}
And for <code> tags inside <a> tags:
a code {
/* Only apply this to code that is a hyperlink */
color: #161616;
background-color: #CD0000;
border-radius: 5px;
padding: 0 2px 0px 2px;
text-decoration:none;
transition: background-color .6s;
-webkit-transition: background-color .6s; /* Safari and Chrome */
-moz-transition: background-color .6s; /* Firefox 4 */
-o-transition: background-color .6s; /* Opera */
}
a code:hover {
background-color:#440000;
border-radius: 5px;
padding: 0 2px 0px 2px;
}
The problem is that when I mouse over a link such as <a href='#'><code>long code</code></a>, the backgrounds of both the <code> and the <a> tags are transformed. Here are two images that illustrate this. In the first image, I managed to put the pointer of the mouse only over the <a> element. In the second, the mouse is over the <code> element:
Is there a way to style <code> links differently from normal links? Thank you in advance.
I think you're looking for the contains() selector, which is no longer part of the css3 selector spec
To achieve this, you will want to look at either a js framework solution, like has() in jquery or a dynamic css solution, such as less