Center element with animation / transition - javascript

I'm trying to create a full screen menu that does a bottom-to-top movement and I'm having trouble when it comes to vertically centering it.
Basically, it comes out of the screen and should end up right in the middle of it (centered).
However, since it is a fixed menu with an unknown height and I'm using animations, the options available aren't many:
I can't use the margin: auto technique because the auto value doesn't work with transitions;
I'm trying to avoid using flexbox;
translateY() seems to work fine but it creates a top-to-bottom movement instead of the desired bottom-to-top one (see my code)
anything else? (preferably that works with older browsers, but I can also manage with using translateY if there's a way to change the direction)
$('#small-nav-btn').click(function() {
$('#overlay').addClass('open');
$('#close-menu-cross').addClass('open');
$('#nav').addClass('open');
})
$('#cross').click(function() {
$('#overlay').removeClass('open');
$('#close-menu-cross').removeClass('open');
$('#nav').removeClass('open');
})
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Now-Regular", sans-serif;
font-size: 12px;
font-weight: bold;
}
ul {
list-style-type: none;
}
a {
color: black;
}
#overlay {
background: #fff;
opacity: 0;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 0;
transition: all 1s ease 0s;
z-index: 1555;
}
#overlay.open {
opacity: 1;
height: 100%;
}
#small-nav-bar {
display: block;
width: 80%;
margin: 0 auto;
text-align: center;
color: black;
}
#small-nav-btn {
cursor: pointer;
}
#nav {
background: orange;
position: fixed;
top: -100%; /*I need it to be bottom: -100% for the bottom-top movement*/
left: 50%;
transform: translate(-50%, -50%);
transition: all 0.8s linear 0.1s;
z-index: 1556;
}
#nav.open {
top: 50%; /*Again, I need this to be bottom: 50%*/
}
#close-menu-cross.open {
display: block;
position: fixed;
top: 15px;
right: 20px;
z-index: 1556;
cursor: pointer;
}
#close-menu-cross {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="nav-container">
<div id="small-nav-bar">
<div id="small-nav-btn">BUTTON</div>
</div>
<ul id="nav">
<li><span>HELLO</span>
</li>
<li><span>HELLO</span>
</li>
</ul>
<div id="close-menu-cross">
<div id="cross">X</div>
</div>
</nav>
jsfiddle
Thanks in advance! :)

You were quite close. With just a few adjustments in the CSS, you have a full working demo:
$('#small-nav-btn').click(function() {
$('#overlay').addClass('open');
$('#close-menu-cross').addClass('open');
$('#nav').addClass('open');
})
$('#cross').click(function() {
$('#overlay').removeClass('open');
$('#close-menu-cross').removeClass('open');
$('#nav').removeClass('open');
})
#nav {
background: orange;
position: fixed;
top: 100%; /* 1 */
left: 50%;
transform: translate(-50%, 0); /* 2 */
transition: all 0.8s linear 0.1s;
z-index: 1556;
}
#nav.open {
top: 50%;
transform: translate(-50%, -50%); /* 2 */
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Now-Regular", sans-serif;
font-size: 12px;
font-weight: bold;
}
ul {
list-style-type: none;
}
a {
color: black;
}
#overlay {
background: #fff;
opacity: 0;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 0;
transition: all 1s ease 0s;
z-index: 1555;
}
#overlay.open {
opacity: 1;
height: 100%;
}
#small-nav-bar {
display: block;
width: 80%;
margin: 0 auto;
text-align: center;
color: black;
}
#small-nav-btn {
cursor: pointer;
}
#close-menu-cross.open {
display: block;
position: fixed;
top: 15px;
right: 20px;
z-index: 1556;
cursor: pointer;
}
#close-menu-cross {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<nav id="nav-container">
<div id="small-nav-bar">
<div id="small-nav-btn">BUTTON</div></div>
<ul id="nav">
<li><span>HELLO</span></li>
<li><span>HELLO</span></li>
</ul>
<div id="close-menu-cross">
<div id="cross">X</div>
</div>
</nav>
Notes:
The CSS offset properties (top, bottom, left, right), when applied to absolutely-positioned elements (which includes position: fixed), shift the element x-distance from the respective edge.
You have top: -100% in your code. This puts the element 100% above the top edge.
You then have it shifting to top: 50%. This puts the element halfway inside the container.
Essentially, your animation moves the element a distance of 150%, from above the window to inside it. The movement is top to bottom.
But you want the movement to go from bottom to top.
So start the element all the way at the bottom and off-screen (top: 100%), and have it shift up to halfway inside the container (top: 50%).
The transform: translate() rule simply fine-tunes the centering.
If translateY(-50%) is applied to the primary state (like in your code), it will shift 50% of the nav onto the screen before the transition (demo).
That's why I applied translateY(-50%) only to the transitioned state.
For a complete explanation see my answer here: Element will not stay centered, especially when re-sizing screen
jsFiddle

Related

Transition going only one way when removing class

I'm trying to create a sidebar like this:
function show_menu(){
document.querySelector('#sidebar').classList.toggle('sidebar_open');
document.querySelector('#blackscreen').classList.toggle('blackscreen_open');
}
#hamburger {
width: 5rem;
height: 5rem;
background-color: red;
}
#header_wrapper {
display: block;
}
#sidebar {
position: fixed;
top: 0;
right: 0;
width: 0;
height: 100vh;
transition: width 0.25s;
}
.sidebar_open {
width: 50% !important;
background-color: green;
display: grid !important;
}
#blackscreen{
position:fixed;
top:0;
right:0;
width:100vw;
height:100vh;
transition: background-color 0.25s;
}
.blackscreen_open {
background-color: rgba(0, 0, 0, 0.5);
}
<div id="hamburger" onclick="show_menu()">click here</div>
<div id='blackscreen' onclick="show_menu()"></div>
<span id='sidebar'>
</span>
but the sidebar transition only works one way,
My best guess is that the transition requires 2 classes to operate
but since I've removed one class the transition back might not be taking place
so I tried the solution according to this question like this:
function show_menu(){
document.querySelector('#sidebar').classList.toggle('sidebar_open');
document.querySelector('#blackscreen').classList.toggle('blackscreen_open');
}
#hamburger {
width: 5rem;
height: 5rem;
background-color: red;
}
#header_wrapper {
display: block;
}
#sidebar {
position: fixed;
top: 0;
right: 0;
width: 0;
height: 100vh;
}
#sidebar:not(.sidebar_open) {
transition: width 0.25s;
}
.sidebar_open {
width: 50% !important;
background-color: green;
display: grid !important;
}
#blackscreen {
position: fixed;
top: 0;
right: 0;
width: 100vw;
height: 100vh;
transition: background-color 0.25s;
}
.blackscreen_open {
background-color: rgba(0, 0, 0, 0.5);
}
<div id="hamburger" onclick="show_menu()">click here</div>
<div id='blackscreen' onclick="show_menu()"></div>
<span id='sidebar'>
</span>
but as you can see even that didn't work
I'm pretty sure I'm missing something obvious but any & all help is greatly appreciated!
I'm fairly certain that the transition IS working but you're not seeing it because when it's not open, your sidebar doesn't have a background colour, and since you don't have a transition setting for your background attribute, when you remove the sidebar_open class the background colour IMMEDIATELY reverts to none and the width transition becomes invisible.
You should be able to test this by moving background-color: green; from the .sidebar_open class to the #sidebar element.
You need to have the background-color set even when the sidebar is not open. Otherwise, when the class is removed, you can no longer see it even though the width is being animated.
#sidebar {
position: fixed;
top: 0;
right: 0;
width: 0;
height: 100vh;
background-color: green;
transition: width 0.25s;
}
function show_menu() {
document.querySelector('#sidebar').classList.toggle('sidebar_open');
document.querySelector('#blackscreen').classList.toggle('blackscreen_open');
}
#hamburger {
width: 5rem;
height: 5rem;
background-color: red;
}
#header_wrapper {
display: block;
}
#sidebar {
position: fixed;
top: 0;
right: 0;
width: 0;
height: 100vh;
background-color: green;
transition: width 0.25s;
}
.sidebar_open {
width: 50% !important;
display: grid !important;
}
#blackscreen {
position: fixed;
top: 0;
right: 0;
width: 100vw;
height: 100vh;
transition: background-color 0.25s;
}
.blackscreen_open {
background-color: rgba(0, 0, 0, 0.5);
}
<div id="hamburger" onclick="show_menu()">click here</div>
<div id='blackscreen' onclick="show_menu()"></div>
<span id='sidebar'></span>

Mobile menu - CSS / DIV - Dim visible background around menu?

When my mobile menu opens, I would love the rest of the visible background (other than the menu itself) to 'dim.' (Both my pages and menu background are very white in general).
There is a plugin that offers this functionality but in trying to keep the website light, am trying to see if this is possible with just some lines of code?
Googling for quite a while came up with nothing other than the app which is a surprise... maybe I searched the wrong keywords?
Any ideas?
Here is my full code (not my original code, can link various parts to their respective Authors).
/*Change hamburger menu colour*/
span.mobile_menu_bar:before{
color:#D7AF39;
}
/*Remove shading of top menu to match sub menu*/
.et_mobile_menu .menu-item-has-children a {
background-color:#FFFFFF;
}
/** Divi Space slide in mobile edits**/
#mobile_menu { display: block !important; min-height: 100vh; top: 0; border-top: none; padding-top: 80px; z-index: 9998; }
.mobile_nav.closed #mobile_menu {
transform: rotateY(90deg); -webkit-transform: rotateY(90deg);
transform-origin: right; -webkit-transform-origin: right;
background: #fff; transition: .8s ease-in-out !important; }
.mobile_nav.opened #mobile_menu {
transform: rotateY(0deg); -webkit-transform: rotateY(0deg);
transform-origin: right; -webkit-transform-origin: right;
background: #fff; transition: .8s ease-in-out; }
.mobile_nav.opened .mobile_menu_bar:before {
content: "\4d"; color: #D7AF39; }
.et_mobile_menu li a, .et_mobile_menu .menu-item-has-children>a {
font-weight: 600;
font-family: open sans;
font-size: large;
}
#media(max-width: 980px) {
.et_header_style_split .mobile_menu_bar, .et_header_style_left .mobile_menu_bar { z-index: 9999; }
#main-header .container.clearfix.et_menu_container { width: 100%; }
.logo_container { padding-left: 30px; }
#et-top-navigation { padding-right: 30px; }
}
#media(min-width: 341px) {
#mobile_menu { width: 340px; margin-left: calc(100% - 340px); }
}
One way of doing this is to assert a blanket div over the entire page, beginning just below the menu bar, then setting that div's opacity to the desired level of dimming.
I have thrown together a very simple proof of concept. Hover the dummy Menu button to observe the effect. Take it onwards from there.
body {
--menu-height: 50px;
}
#page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: yellow;
}
#menu_bar {
position: absolute;
width: 100%;
height: var( --menu-height);
background-color: blue;
}
#menu_item {
position: absolute;
top: 10px;
left: 10px;
width: 50px;
height: 30px;
background-color: white;
line-height: 30px;
text-align: center;
}
#menu_item:hover:after {
content: '';
position: fixed;
top: var( --menu-height);
left: 0;
height: 100vh;
width: 100vw;
background-color: black;
opacity: 0.5;
/* Ensure z-index is higher than page's content/data items */
z-index: 2
}
#data {
position: absolute;
top: 25%;
left: 25%;
width: 50%;
height: 50%;
background-color: white;
border: 2px solid black;
padding: 10px;
z-index: 1;
}
#text {
display: inline-block;
width: 100%;
text-align: center;
font-weight: bold;
}
<div id="page">
<div id="menu_bar">
<div id="menu_item">Menu</div>
</div>
<div id="data">
<span id="text">Hover the "Menu" button...</span><br><br> Lorem ipsum dolor etc
</div>
</div>

Text fill effect - simulate a block of color passing through text

I'm trying to create a text effect so when you hover over text, a block of color seems to pass through the text.
I followed exactly what the first example here (for the word "Kukuri") does by using a :before pseudo-element to achieve the color fill. I have the code written here in SCSS:
.text {
position: relative;
&:hover {
&:before {
width: 100%;
}
}
&:before {
content: 'HELLO'; // if our text was "HELLO"
width: 0%;
position: absolute;
z-index: 2;
overflow: hidden;
color: red;
transition: width 350ms ease-in-out;
white-space: nowrap;
width: 0%;
}
}
However, I'm wondering if it's possible to animate the :before element's width the other way? So once it hits 100% width and fills with color, then the left side starts emptying and it goes back to 0% fill.
The end goal is to use this for a navigation menu. Something like this effect where it seems like a block of color is moving through menu items when you hover:
For something like this, hovering over "About" item would make the fill color wipe down while
Attempted Solutions
I tried translating the :before element, changing the left and right properties, and changing transform-origin to no avail.
I've tried looking into mix-blend-mode to try and create a rectangular mask that could potentially add color to the text. However, to my understanding, mix-blend-mode only works with text and not with rectangular divs with background-colors.
You may do something like this by simply using a layer that pass above your element with transparent background:
ul {
list-style: none;
padding: 0;
margin: 0;
}
ul li {
display: inline-block;
margin: 10px;
position: relative;
font-size: 30px;
color: red;
font-weight: bold;
overflow: hidden;
}
ul li:before {
content: "";
position: absolute;
height: 100%;
width: 100%;
background: rgba(255, 255, 255, 0.6);
transition: 2s;
z-index: 2;
}
ul.ver li:before {
top: 0;
left: -100%;
}
ul.hor li:before {
top: -100%;
left: 0;
}
ul.ver li:hover::before {
left: 100%;
}
ul.ver.half li:hover::before {
left: 0;
}
ul.hor li:hover::before {
top: 100%;
}
ul.hor.half li:hover::before {
top: 0;
}
<ul class="hor">
<li>Home</li>
<li>About</li>
</ul>
<ul class="hor">
<li>Home</li>
<li>About</li>
</ul>
<ul class="ver half">
<li>Home</li>
<li>About</li>
</ul>
<ul class="hor half">
<li>Home</li>
<li>About</li>
</ul>
And here is another example using mix-blend-mode with text:
ul {
list-style: none;
padding: 0;
margin: 0;
}
ul li {
display: inline-block;
margin: 10px;
position: relative;
font-size: 30px;
background-image: linear-gradient(to right, red, red);
background-size: 200% 200%;
background-repeat: no-repeat;
font-weight: bold;
overflow: hidden;
transition: 1s;
}
ul.hor li {
background-position: 0% 200%;
}
ul.ver li {
background-position: 200% 0%;
}
ul li span {
display: inline-block;
color: black;
background-color: white;
mix-blend-mode: screen;
}
ul.hor li:hover {
background-position: 0% -100%;
}
ul.ver li:hover {
background-position:-100% 0%;
}
ul.hor.half li:hover {
background-position: 0% 0%;
}
ul.ver.half li:hover {
background-position:0% 0%;
}
<ul class="hor">
<li><span>Home</span></li>
<li><span>About</span></li>
</ul>
<ul class="ver">
<li><span>Home</span></li>
<li><span>About</span></li>
</ul>
<ul class="hor half">
<li><span>Home</span></li>
<li><span>About</span></li>
</ul>
<ul class="ver half">
<li><span>Home</span></li>
<li><span>About</span></li>
</ul>
You can use blend modes for this effect, here you have one posibility:
I opted for moving the background of the pseudo rather than moving the pseudo itself, This way you won't have side effects when the pseudo is over other elements.
Also, it isn't clear for me if you want a single slide or a double one. I have set it to be a double one ( from black to red and again to black. You can change this easily adjusting the final background position
.demo {
background-color: yellow;
margin: 10px;
display: inline-block;
font-size: 50px;
padding: 10px;
position: relative;
}
.demo:after {
content: "";
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, transparent 25%, red 25%, red 75%, transparent 75% );
mix-blend-mode: lighten;
background-size: 400% 100%;
transition: background-position 2s linear;
background-position: 100% 0%;
}
.demo:hover:after {
background-position: 0% 0%;
}
<div class="demo">TEST1</div>
<div class="demo">TEST2</div>
To change the movement to vertical, you need to change
the gradient direction
which of the image dimensions is oversized
the background position that is changed on hover
.demo {
background-color: yellow;
margin: 10px;
display: inline-block;
font-size: 50px;
padding: 10px;
position: relative;
}
.demo:after {
content: "";
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
background-image: linear-gradient(to top, transparent 25%, red 25%, red 75%, transparent 75% );
mix-blend-mode: lighten;
background-size: 100% 400%; /* changed vertical dimension */
transition: background-position 2s linear;
background-position: 0% 100%; /* changed 100 position to vertical*/
}
.demo:hover:after {
background-position: 0% 0%;
}
<div class="demo">TEST1</div>
<div class="demo">TEST2</div>
.text {
text-transform: uppercase;
font-weight: 900;
overflow: hidden;
line-height: 0.75;
color: #c5c2b8;
position:relative;
}
.text:before {
content: attr(data-letters);
position: absolute;
z-index: 2;
overflow: hidden;
color: red;
white-space: nowrap;
width: 0%;
top:0;
-webkit-transition: width 0.4s 0.3s;
transition: width 0.4s 0.3s;
}
.text:hover:before {
width: 100%;
}
<span class="text" href="#" data-letters="hello">hello</span>

Responsive Hover Box over Responsive Image

I've been using a tutorial for making a hover box go over a set of images.
The article can be found here.
Got it working perfectly, except I want my images and the hover to be responsive to window size (just via width is fine), I've tried looking up how to do this. Seems like it might be a case of using % rather than a fixed value, but not experienced enough to know how to execute the markup. Even if I get the images to re-size the hover box doesn't re-size with them.
Is it possible to add something to the existing CSS to make this happen.
ul.img-list {
list-style-type: none;
margin: 0;
padding: 0;
text-align: center;
}
ul.img-list li {
display: inline-block;
height: 150px;
margin: 0 1em 1em 0;
position: relative;
width: 150px;
}
span.text-content {
background: rgba(0,0,0,0.5);
color: white;
cursor: pointer;
display: table;
height: 150px;
left: 0;
position: absolute;
top: 0;
width: 150px;
opacity: 0;
-webkit-transition: opacity 500ms;
-moz-transition: opacity 500ms;
-o-transition: opacity 500ms;
transition: opacity 500ms;
}
span.text-content span {
display: table-cell;
text-align: center;
vertical-align: middle;
}
ul.img-list li:hover span.text-content {
opacity: 1;
}
<ul class="img-list">
<li>
<a href="#">
<img src="http://placehold.it/150x150">
<span class="text-content"><span>Text</span></span>
</a>
</li>
</ul>
you can use only width to keep image ratio.
you can use display:block for a and img, and use flex to center text.
not too sure about the responsive behavior you look for for, you can use a % width on li or a mix a % width + min-width and max-width.
example with % width set at 50% (and max/min width ) , it can be any other value and units.
ul.img-list {
list-style-type: none;
margin: 0;
padding: 0;
text-align: center;
}
ul.img-list li {
display: inline-block;
vertical-align:top;
margin: 0;
position: relative;
width:50%;
max-width:100vh;
min-width:60vh;
}
ul.img-list li a, ul.img-list li a img {
display:block;
width:100%;
}
span.text-content {
background: rgba(0,0,0,0.5);
color: white;
cursor: pointer;
display:flex;
left: 0;
right:0;
position: absolute;
top: 0;
bottom:0;
opacity: 0;
-webkit-transition: opacity 500ms;
-moz-transition: opacity 500ms;
-o-transition: opacity 500ms;
transition: opacity 500ms;
}
span.text-content span {
margin:auto;
}
ul.img-list li:hover span.text-content {
opacity: 1;
}
* {
margin:0;
padding:0;
}
<ul class="img-list">
<li>
<a href="#">
<img src="http://placehold.it/150x150">
<span class="text-content"><span>Text</span></span>
</a>
</li>
</ul>
You could also do it like this, using pseudo elements to display the overlay content on the image. This method is fully responsive.
.wrapper {
width: 100%;
overflow: auto;
padding: 1%;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
/* Columns floated left */
.col-4 {
width: 33.3%;
float: left;
padding: 1%;
box-sizing: border-box;
height: auto;
overflow: hidden;
}
/* Container to make absolute positioning easier on psuedo element */
.image_container {
position: relative;
overflow: auto;
padding: 0;
margin: 0;
}
.image_container img {
width: 100%;
display: block;
}
/* Structure for ::before element */
#img_1::before,
#img_2::before,
#img_3::before {
background: rgba(0, 0, 0, 0.5);
position: absolute;
left: 0;
right: 0;
height: 100%;
width: 100%;
box-sizing: border-box;
display: block;
opacity: 0;
color: white;
padding: 10px;
transition: 0.5s ease;
}
/* Hover state for container to show ::before on mouseover */
.image_container:hover#img_1::before,
.image_container:hover#img_2::before,
.image_container:hover#img_3::before {
opacity: 1;
cursor: pointer;
}
/* Text for ::before elements */
#img_1::before {
content: 'Image Title 1';
}
#img_2::before {
content: 'Image Title 2';
}
#img_3::before {
content: 'Image Title 3';
}
<div class="wrapper">
<div class="col-4">
<div class="image_container" id="img_1">
<img src="https://images.unsplash.com/reserve/B6PfiQ8QoSzmsZYOCkSB__DSC0530-1.jpg?dpr=1&auto=format&fit=crop&w=1500&h=1004&q=80&cs=tinysrgb&crop=">
</div>
</div>
<div class="col-4">
<div class="image_container" id="img_2">
<img src="https://images.unsplash.com/photo-1418985991508-e47386d96a71?dpr=1&auto=format&fit=crop&w=1500&h=1000&q=80&cs=tinysrgb&crop=">
</div>
</div>
<div class="col-4">
<div class="image_container" id="img_3">
<img src="https://images.unsplash.com/photo-1476362555312-ab9e108a0b7e?dpr=1&auto=format&fit=crop&w=1500&h=1000&q=80&cs=tinysrgb&crop=">
</div>
</div>
</div>
You could also set the images as a background-image for the div which would give you more control over the text in the overlay, if you needed it.

How to morph a plus sign to a minus sign using CSS transition?

I want to create a toggle button that morphs its shape from a plus sign to a minus sign.
Using CSS only, without the use of pseudo-elements.
My desired effect is to have the vertical line in the "+" sign to shrink into the horizontal line.
I know it's possible but I'm not sure which is the best route to take. I was thinking of doing something with the height but I'm worried about the line-height of browsers changing its position in the element.
$('button').on("click", function(){
$(this).toggleClass('active');
});
button {
color: #ecf0f1;
background: #e74c3c;
width: 50px;
height: 50px;
border: 0;
font-size: 1.5em;
}
button span {
transition: all .75s ease-in-out;
}
button.active span {
/* Code to morph + to - */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button><span>+</span></button>
Because of the simplicity of the shapes, the easiest way is just to make the + and - with elements. Using pseudo elements would be the cleanest solution, but you can also just use a DOM element and have a slightly messier document structure.
With that in mind, the actual solution is straightforward. We use CSS to position elements to resemble the desired characters, and then "morph" between them by animating that position.
Take a look over the following code, and try to understand what each rule is accomplishing.
button {
color: #ecf0f1;
background: #e74c3c;
width: 50px;
height: 50px;
border: 0;
font-size: 1.5em;
position: relative;
}
button span {
position: absolute;
transition: 300ms;
background: white;
border-radius: 2px;
}
/* Create the "+" shape by positioning the spans absolutely */
button span:first-child {
top: 25%;
bottom: 25%;
width: 10%;
left: 45%;
}
button span:last-child {
left: 25%;
right: 25%;
height: 10%;
top: 45%;
}
/* Morph the shape when the button is hovered over */
button:hover span {
transform: rotate(90deg);
}
button:hover span:last-child {
left: 50%;
right: 50%;
}
<button>
<span></span>
<span></span>
</button>
Note : please stop editing the question making the answers incorrect
CSS solution
$('button').on("click", function(){
$(this).toggleClass('active');
});
button {
color: #ecf0f1;
background: #e74c3c;
width: 70px;
height: 70px;
position: relative;
font-size: 50px;
cursor: pointer;
border: 0;
outline: 0;
padding: 0
}
.plus,
.minus {
color: #fff;
padding: 10px;
width: 70px;
height: 70px;
line-height: 50px;
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
box-sizing: border-box;
transition: .5s all ease-out;
}
.plus {
opacity: 1;
transform: rotate(0deg);
}
button.active .plus {
opacity: 0;
transform: rotate(90deg);
}
.minus {
opacity: 0;
transform: rotate(-90deg);
}
button.active .minus {
opacity: 1;
transform: rotate(0deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<button>
<span class="plus"><i class="fa fa-plus"></i></span>
<span class="minus"><i class="fa fa-minus"></i></span>
</button>
A (old) CSS solution:
Using pseudo element ::before with content property
$('button').on("click", function() {
$(this).toggleClass('active');
});
button {
color: #ecf0f1;
background: #e74c3c;
width: 50px;
height: 50px;
border: 0;
font-size: 1.5em;
}
button span {
transition: all .75s ease-in-out;
position:relative
}
button span::before {
content:"+"
}
button.active span::before {
content:"-"
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button><span></span></button>
A (old) jquery Solution:
no need for span, you can do this using text() with a if statement in jquery
$('button').on("click", function() {
$(this).toggleClass('active');
$(this).text() == "+" ? $(this).text("-") : $(this).text("+");
});
button {
color: #ecf0f1;
background: #e74c3c;
width: 50px;
height: 50px;
border: 0;
font-size: 1.5em;
transition: all .75s ease-in-out;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>+</button>
Ah my bad I've overlooked that OP doesn't want to use any pseudo
elements. But the big advantage with pseudo elements would be that you have less HTML Code and a cleaner structure.
It's also a different morphing animation as OP wants but maybe someone else can use this.
So if you don't mind I'll let my suggestion there.
Maybe something like this?
HTML
<div class="button"></div>
CSS
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: #343838;
}
.button {
position: absolute;
width: 55px;
height: 55px;
background: #70975B;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(0deg);
border-radius: 50%;
cursor: pointer;
z-index: 100;
transition: 0.4s cubic-bezier(0.2, 0.6, 0.3, 1.1);
}
.button:after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 2px;
width: 50%;
background: white;
}
.button:before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 50%;
width: 2px;
background: white;
}
.button.clicked {
transform: translate(-50%, -50%) rotate(360deg);
background: #CC2A41;
}
.button.clicked:before {
width: 0;
}
jQuery
$(".button").click(function() {
$(this).toggleClass("clicked");
});
And here a working example
http://codepen.io/svelts/pen/LkyZoZ
try this
$('button').on("click", function() {
var $this = $(this);
$this.toggleClass('toggle');
if ($this.hasClass('toggle')) {
$this.text('+');
} else {
$this.text('-');
}
});
button {
color: #ecf0f1;
background: #e74c3c;
width: 50px;
height: 50px;
border: 0;
font-size: 1.5em;
transition: all .75s ease-in-out;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button class="toggle">+</button>

Categories