I try to build "read more/less" functionality with fade in/out text.
Since CSS transitions do not work on height: auto;, I am forced to hardcode the value in CSS with my approach. Which could be too small or too big. The fade-in text can vary in length from element to element where the JS will be applied on.
I can read the real height of the <p id="txt"> element which includes the transparent text before any css is applied:
document.addEventListener("DOMContentLoaded", function(event) {...});
So I know about the height I need to transform to before the toggle happens but I can't figure out a way to make it work. Is this even possible with my approach?
Changing the height property right after the toggle with currentText.style.height = realHeight + "px"; does not work since it's too late here.
document.addEventListener("DOMContentLoaded", function(event) {
// Get the height including the transparent text
var realHeight = document.getElementById("txt").scrollHeight;
var heightDisplay = document.getElementById("realHeight");
heightDisplay.innerHTML += realHeight + "px";
});
const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");
wrapper.addEventListener("click", e => {
const current = e.target;
const isReadMoreBtn = current.className.includes("read-more-btn");
if (!isReadMoreBtn)
return;
const currentText = e.target.parentNode.querySelector(".read-more");
currentText.classList.toggle("read-more_open");
// currentText.style.height = realHeight + "px";
current.textContent = current.textContent.includes("Read More") ? "Read Less" : "Read More";
});
/* toggle classes */
.read-more {
display: block;
opacity: 0;
height: 0;
transition: all 0.5s;
}
.read-more_open {
display: block;
opacity: 1;
height: 5em;
}
/* other styles */
.wrapper {
width: 20rem;
background-color: black;
color: white;
margin: auto;
}
.contents {
padding: 0.25rem;
}
.title {
font-size: 1.7em;
margin-bottom: 1rem;
letter-spacing: 1px;
}
p {
font-size: 0.8rem;
}
.read-more-btn {
display: inline-block;
color: white;
background-color: grey;
padding: 0.6rem 1.5rem;
font-size: 1em;
position: relative;
z-index: 1;
transition: all 0.5s;
cursor: pointer;
}
.read-more-btn:hover {
background-color: #fff;
color: grey;
box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>
<div class="wrapper">
<div class="contents">
<h1 class="title">Some Title</h1>
<p id="txt">
ALWAYS VISIBLE CONTENT
<span class="read-more">
Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in an unknown height of the paragraph.
</span>
</p>
<p class="read-more-btn">Read More</p>
</div>
</div>
Solution 1
Use max-height instead, and add a value that is bigger than expected to make sure that the content fits.
Edited:
.read-more {
max-height: 0;
}
.read-more_open {
max-height: 500px;
}
Check JavaScript
The Code:
const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");
btn.addEventListener("click", e => {
// toggle the class 'read-more_open'
text.classList.toggle("read-more_open");
// if `text` contains the class 'read-more_open'
//set `btn` HTML to 'Read Less', else? set to 'Read More'
text.classList.contains("read-more_open") ? btn.innerHTML = "Read Less" : btn.innerHTML = "Read More";
});
/* toggle classes */
.read-more {
display: block;
opacity: 0;
max-height: 0;
transition: all 0.5s;
}
.read-more_open {
display: block;
opacity: 1;
max-height: 500px;
}
/* other styles */
.wrapper {
width: 20rem;
background-color: black;
color: white;
margin: auto;
}
.contents {
padding: 0.25rem;
}
.title {
font-size: 1.7em;
margin-bottom: 1rem;
letter-spacing: 1px;
}
p {
font-size: 0.8rem;
}
.read-more-btn {
display: inline-block;
color: white;
background-color: grey;
padding: 0.6rem 1.5rem;
font-size: 1em;
position: relative;
z-index: 1;
transition: all 0.5s;
cursor: pointer;
}
.read-more-btn:hover {
background-color: #fff;
color: grey;
box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>
<div class="wrapper">
<div class="contents">
<h1 class="title">Some Title</h1>
<p id="txt">
ALWAYS VISIBLE CONTENT
<span class="read-more">
Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with unknown length which results in unknown height of the paragraph.
</span>
</p>
<p class="read-more-btn">Read More</p>
</div>
</div>
Solution 2
If the height value in px is important, you can get the full height first, and then hide the text and set max-height to 0 and show the wrapper. (The user won't notice the transition since wrapper has no transition property).
CSS Edited:
.wrapper {
opacity: 0;
}
.wrapper.show {
opacity: 1;
}
The Code:
const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
const wrapper = document.querySelector(".wrapper");
const realHeightEl = document.getElementById('realHeight');
// Variable to hold the exact height of wrapper
let h;
window.addEventListener('load', () => {
// get text height
h = text.getBoundingClientRect().height;
// show height in HTML `realHeight` element
realHeightEl.innerHTML += `${h}px`;
// show the wrapper
wrapper.classList.add('show');
// hide the text
text.style.maxHeight = '0';
});
btn.addEventListener("click", () => {
// if the height is 0:
// set it to `h` (text `height`), `opacity` 1, and change `btn` text
if(text.style.maxHeight == '0px'){
text.style.maxHeight = h + 'px';
text.style.opacity = '1';
btn.innerHTML = "Read Less";
} else {
// else ? set the `height` and `opacity` to `0` and change btn text.
text.style.maxHeight = '0px';
text.style.opacity = '0';
btn.innerHTML = "Read More";
}
});
/* toggle classes */
.read-more {
display: block;
opacity: 0;
max-height: fit-content;
transition: all 0.5s;
}
.read-more_open {
display: block;
opacity: 1;
}
/* other styles */
.wrapper {
width: 20rem;
background-color: black;
color: white;
margin: auto;
opacity: 0;
}
.wrapper.show {
opacity: 1;
}
.contents {
padding: 0.25rem;
}
.title {
font-size: 1.7em;
margin-bottom: 1rem;
letter-spacing: 1px;
}
p {
font-size: 0.8rem;
}
.read-more-btn {
display: inline-block;
color: white;
background-color: grey;
padding: 0.6rem 1.5rem;
font-size: 1em;
position: relative;
z-index: 1;
transition: all 0.5s;
cursor: pointer;
}
.read-more-btn:hover {
background-color: #fff;
color: grey;
box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
transform: translateY(-3px);
}
<span id="realHeight">Real height: </span>
<div class="wrapper">
<div class="contents">
<h1 class="title">Some Title</h1>
<p id="txt">
ALWAYS VISIBLE CONTENT
<span class="read-more">
Fade in text with unknown length which results in unknown height of the paragraph. Fade in text with
unknown length which results in unknown height of the paragraph. Fade in text with unknown length which
results in unknown height of the paragraph. Fade in text with unknown length which results in unknown
height of the paragraph. Fade in text with unknown length which results in unknown height of the
paragraph.
</span>
</p>
<p class="read-more-btn">Read More</p>
</div>
</div>
Note:
the h variable holds the max-height value to set in JavaScript, in short, it's the same functionality in Solution 1.
// This CSS declaration is deleted in Solution2
.read-more_open {
max-height: 500px;
}
That means, in Solution2:
Replaced:
This in JavaScript -> max-height: h;
Instead of this in CSS: max-height: 500px;
This way we make sure that the max-height is dynamically declared no matter what is the height value of the text.
Used .contains to check if is contains the class read-more_open :
If yes then height is changed to scroll height
If no height is changed back to 0px
var heightDisplay = document.getElementById("realHeight");
var realHeight = document.getElementById("txt").scrollHeight;
const btn = document.querySelector(".read-more-btn");
const text = document.querySelector(".read-more");
btn.addEventListener("click", e => {
text.classList.toggle("read-more_open");
if (text.classList.contains("read-more_open")) {
heightDisplay.innerHTML = "Yes your text height matter to us";
document.querySelector(".read-more").style.height = realHeight + "px";
} else {
heightDisplay.innerHTML = "You are back where you started in height";
document.querySelector(".read-more").style.height = "0px";
}
btn.textContent = btn.textContent.includes("Read More") ? "Read Less" : "Read More";
});
/* toggle classes */
.read-more {
display: block;
opacity: 0;
height: 0;
transition: all 0.5s;
}
.read-more_open {
display: block;
opacity: 1;
}
/* other styles */
.wrapper {
width: 20rem;
background-color: black;
color: white;
margin: auto;
}
.contents {
padding: 0.25rem;
}
.title {
font-size: 1.7em;
margin-bottom: 1rem;
letter-spacing: 1px;
}
p {
font-size: 0.8rem;
}
.read-more-btn {
display: inline-block;
color: white;
background-color: grey;
padding: 0.6rem 1.5rem;
font-size: 1em;
position: relative;
z-index: 1;
transition: all 0.5s;
cursor: pointer;
}
.read-more-btn:hover {
background-color: #fff;
color: grey;
box-shadow: 0 0 2 rem rgba(0, 0, 0, 0.1);
transform: translateY(-3px);
}
<span id="realHeight">Click Read more to see more </span>
<div class="wrapper">
<div class="contents">
<h1 class="title">Some Title</h1>
<p id="txt">
ALWAYS VISIBLE CONTENT
<span class="read-more">
Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in the unknown height of the paragraph. Fade in text with an unknown length which results in an unknown height of the paragraph.
</span>
</p>
<p class="read-more-btn">Read More</p>
</div>
</div>
Related
I'm using the following code to change prices in my pricing table:
function myFunction() {
var x = document.getElementById("prijs-small");
if (x.innerHTML === "17,50") {
x.innerHTML = "13,50";
} else {
x.innerHTML = "17,50";
}
var x = document.getElementById("prijs-medium");
if (x.innerHTML === "58") {
x.innerHTML = "30,50";
} else {
x.innerHTML = "58";
}
var x = document.getElementById("prijs-large");
if (x.innerHTML === "128,50") {
x.innerHTML = "61";
} else {
x.innerHTML = "128,50";
}
}
.prijs-button {
background-color: white;
color: #012d5d;
padding: 10px 20px;
border-radius: 30px;
}
<button class="prijs-button" onclick="myFunction()">150/350</button>
<div id="prijs-small">17,50</div>
<div id="prijs-medium">58</div>
<div id="prijs-large">128,50</div>
Now I want to style a toggle that does the same, with on the one side showing 150 (people icon) and other side 350 (people icon) as shown in this example:
As I'm using a button now to make the prices change, how can I turn this into this toggle? The green half should be the active part.
Thanks so much in advance!
Will this do? I've used a checkbox and styled it to look like a pill. Hopefully it's self-explanatory but if not drop me a comment and I'll elaborate.
body {
background-color: gray;
}
#mybutton {
width: 8rem;
height: 3rem;
border-radius: 100rem;
overflow: hidden;
}
#mybutton input {
display: none;
}
#mybutton>label {
display: flex;
}
.side {
width: 50%;
padding-inline: 0.15rem;
text-align: center;
line-height: 3rem;
background-color: white;
transition: background-color, color 300ms;
cursor: pointer;
}
#mybutton input:not(:checked)~.left {
background-color: #35CB8C;
color: white;
transition: background-color, color 300ms;
}
#mybutton input:checked~.right {
background-color: #35CB8C;
color: white;
transition: background-color, color 300ms;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<div id='mybutton'>
<label for='mybutton-checkbox'>
<input type=checkbox id='mybutton-checkbox'>
<div class='side left'>25<i class="fa fa-user" aria-hidden="true"></i></div>
<div class='side right'>100<i class="fa fa-user" aria-hidden="true"></i></div>
</label>
</div>
You can put two buttons next to each other with some border radiuses applied to them.
Some POC version here:
div.toggle {
font-size: 0; /* this is to remove empty inline space between two buttons. if you don't like it, you can use flexbox */
}
button {
width: 100px;
height: 50px;
font-size: 14px;
}
button.left {
border-right-width: 0;
border-radius: 20px 0 0 20px;
}
button.right {
border-radius: 0 20px 20px 0;
}
<div class="toggle">
<button class="left">25</button>
<button class="right">100</button>
</div>
Adding an image or icon inside button is totally possible so you can do if if you want.
By inserting two buttons with different images you might get the result you are seeking.
Then by using in CSS border-bottom-left: (The amount of px you want)px and border-top-left: (The amount of px you want)px you can style you buttons.
THE WHOLE CODE IN JSFIDDLE
I have been struggling to effectively remove the code and css created in the function Seifenblasen_blasen()
function Seifenblasen_blasen(){
btn1.style.display = 'none';
document.getElementById("text").innerHTML="Bubble"
const section = document.querySelector('section')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
},8000)
}
const Blaseninterval = setInterval(Seifenblasen_blasen, 100)
created CSS:
section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
section.text{
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
background-color:#1F69FA;
pointer-events: none;
border: none;
}
section spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 4s linear infinite;
}
section spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
section span {
margin-top: 700px;
font-size: 1em;
color: #333;
margin: 0 auto;
font-family: consolas;
background-color: #1F69FA;
border: none;
position: absolute;
}
HTML:
<section id="section">
<div class="content">
<button id="btn"></button>
<button id="btn1"></button>
</div>
</section>
to then execute the next function function next(). This removal is needed because when I don't remove the elements from the first function the second wont work. I could just do document.head.innerHTML = "" but that would then also remove the css needed for the button appearing in the next function. So then I tried to make variables with const
const btn = document.getElementById('text');
const btn1 = document.getElementById('text1');
const section = document.querySelector('section')
// in function Seifenblasen_blasen()
btn1.style.display = 'none';
// in function next()
section.style.display = 'none';
btn.style.display = 'none';
btn1.style.display = 'block';
to hide and show only parts of the css without removing the css entirely to keep the styling intact, but now nothing works anymore.(the button on the next Screen doesn't show up at all and the first button does not contain any styling) My endgoal is that I can essentially switch between two screens one showing the bubbles and one the bouncy balls and when I click on the button it goes on. (for example start is bubbles. I click -> Bounce, click again -> back to Bubbles and so on)
So I want to replace the text inside of an input field with a h1 tag as soon as the user hits submit because i want the text to have an animation but i can't animate the text inside the text field.
I linked the code pen project version of it to make it easier then organizing all the code in here. I added all the code I had so I wouldn't leave anything out although some of it may be irrelevant.
Basically I want the h1 tag to appear exactly where the input text was so it looks like nothing ever got replaced.
https://codepen.io/timvancowabunga/pen/rNOqdYd
$(document).ready(function() {
$('#btn1').click(function() {
$('#test').text($("#message").val());
$('#message').val('');
$('#test').val('');
});
});
function onTextClick() {
document.getElementById('btn1').className = "show";
}
function showButton() {
document.getElementById('btn1').style.display = 'block';
}
function showSendButton() {
document.getElementById('btn2').style.display = 'block';
}
function formCheck() {
var input = $('#message').val();
if (input == '') {
alert("Please Submit a Valid Message");
} else {
hideButton();
showSendButton();
}
}
function hideButton() {
document.getElementById('btn1').style.display = 'none';
}
function hideSendButton() {
document.getElementById('btn2').style.display = 'none';
document.getElementById('sent').style.display = 'block';
}
function myMove() {
var textWrapper = document.querySelector('.ml13');
textWrapper.innerHTML = textWrapper.textContent.replace(/\S/g, "<span class='letter'>$&</span>");
anime.timeline()
.add({
targets: '.ml13 .letter',
translateY: [0, -1600],
opacity: [1, 0],
easing: "easeInSine",
duration: 3600,
delay: (el, i) => 800 + 60 * i
});
}
body {
background-color: #368670;
font-family: sans-serif;
}
.ml13,
.ml14,
.ml15 {
font-size: 1.9em;
text-transform: uppercase;
letter-spacing: 0.2em;
font-weight: 600;
}
.ml15 {
letter-spacing: 0em;
text-align: center;
}
.ml13 .letter {
display: inline-block;
line-height: 1em;
}
.line {
display: flex;
margin: auto;
width: 50%;
margin-top: 500px;
}
.wrappy {
position: relative;
}
.wrappy h1 {
position: absolute;
left: 48.5%;
top: 20%
}
.butt {
padding-top: 50px;
display: flex;
margin: 0 auto;
width: 100%;
}
#btn1,
#btn2 {
display: table;
margin: 0 auto;
}
input {
z-index: 1000;
margin-left: 10%;
width: 80%;
background: transparent;
border: 0;
border-bottom: 1px solid;
padding: 1em 0 .1em;
text-align: center;
font-size: 18px;
font-family: inherit;
font-weight: 300;
line-height: 1.5;
color: inherit;
outline: none;
}
input:focus {
border-color: #ffffff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js"></script>
<div class="truth">
<!-- <div class="line"> -->
<div class="message-box">
<form class="message-form">
<h2 class="ml15" for="message">TELL A TRUTH</h2>
<div class="wrappy">
<input type="text" id="message" name="message" autocomplete="off" class="ml14">
<!-- <h1 id="test" class="ml13">I love your music!</h1> -->
<h1 id="test" class="ml13"></h1>
</div>
</form>
</div>
<div class="butt">
<button id="btn1" onclick="formCheck();">Ready to Send?</button>
<button id="btn2" style="display: none" onclick="myMove(); setTimeout(showButton, 3000); hideSendButton();">Send!</button>
</div>
</div>
You want to put H1 below the input.
Then you make the text input transparent. Bind the input value to h1.
So in effect when user clicks and type, they are selecting the input and changing its value, but it's transparent, to be shown by the h1 below the input that you will eventually animate.
Also because you mentioned you want it to display correctly in all platforms. You then have to be cognisant of the default behaviours of DOM and CSS properties. If you alter them to get what you want without knowing its natural order, you can get unexpected behaviour and reduce cross-browser compatibility. I have made changes to reflect that.
$(document).ready(function () {
$("#btn1").click(function () {
$("#test").text($("#message").val());
$("#message").val("");
$("#test").val("");
});
});
function onTextClick() {
document.getElementById("btn1").className = "show";
}
function showButton() {
document.getElementById("btn1").style.display = "block";
}
function showSendButton() {
document.getElementById("btn2").style.display = "block";
}
function formCheck() {
var input = $("#message").val();
if (input == "") {
alert("Please Submit a Valid Message");
} else {
hideButton();
showSendButton();
}
}
function hideButton() {
document.getElementById("btn1").style.display = "none";
}
function hideSendButton() {
document.getElementById("btn2").style.display = "none";
document.getElementById("sent").style.display = "block";
}
// attach this to bind h1 to the input value at all times.
$("#message").keyup(function () {
var self = this;
$("#test").text($(this).val());
});
function myMove() {
var textWrapper = document.querySelector(".ml13");
textWrapper.innerHTML = textWrapper.textContent.replace(
/\S/g,
"<span class='letter'>$&</span>"
);
anime.timeline().add({
targets: ".ml13 .letter",
translateY: [0, -1600],
opacity: [1, 0],
easing: "easeInSine",
duration: 3600,
delay: (el, i) => 800 + 60 * i
});
}
body {
background-color: #368670;
font-family: sans-serif;
}
.ml13,
.ml14,
.ml15 {
font-size: 1.9em;
text-transform: uppercase;
letter-spacing: 0.2em;
font-weight: 600;
}
.ml15 {
letter-spacing: 0em;
text-align: center;
}
.ml13 .letter {
display: inline-block;
line-height: 1em;
}
.line {
display: flex;
margin: auto;
width: 50%;
margin-top: 500px;
}
.wrappy {
position: relative;
text-align: center;
}
.wrappy h1 {
position: absolute; /* you then want to give wrappy h1 this to make it occupy no space. */
width: 100%; /* to centralize the text, your option here is to make this 100% width and use text-align */
text-align: center;
padding-top: 21px;
}
.butt {
padding-top: 50px;
display: flex;
margin: 0 auto;
width: 100%;
}
#btn1,
#btn2 {
display: table;
margin: 0 auto;
}
input {
position: relative; /* in order for z-index to work, you need to give an element `position` attribute of value `static`, `relative` or `absolute`. */
z-index: 1000; /* now this will work. wrappy h1 is not given a `z-index` so it defaults to `1`, hence input will be on top of wrappy h1 now. */
width: 80%;
background: transparent;
border: 0;
border-bottom: 1px solid #000; /* you need the line back because we are going to assign color to be transparent */
padding: 35px 0 0 0;
text-align: center;
font-size: 18px;
font-family: inherit;
font-weight: 300;
line-height: 1.5;
color: transparent; /* make the text transparent */
outline: none;
}
input:focus {
border-color: #ffffff;
}
<div class="truth">
<!-- <div class="line"> -->
<div class="message-box">
<form class="message-form">
<h2 class="ml15" for="message">TELL A TRUTH</h2>
<div class="wrappy">
<!-- for natural flow, you want to shift #test to above the input, so that input can stack on top of it -->
<h1 id="test" class="ml13"></h1>
<input type="text" id="message" name="message" autocomplete="off" class="ml14">
<!-- <h1 id="test" class="ml13">I love your music!</h1> -->
</div>
</form>
</div>
<div class="butt">
<button id="btn1" onclick="formCheck();">Ready to Send?</button>
<button id="btn2" style="display: none" onclick="myMove(); setTimeout(showButton, 3000); hideSendButton();">Send!</button>
</div>
</div>
I have two buttons, when a user clicks on them it gets underlined. However, I'd like the .underline to be animated/glide horizontally to the button that is being clicked on.
Demo: https://jsfiddle.net/ds1wr736/11/
As of right now, the .underline just appears and disapears when a button is clicked. How can I animate this to smoothly glide (x values changing) to the selected button without hacks and JQuery?
function switchTab(tab) {
if (tab === 1) {
document.getElementById("tab2").classList.add("underline");
document.getElementById("tab1").classList.remove("underline");
}
else if (tab === 2) {
document.getElementById("tab1").classList.add("underline");
document.getElementById("tab2").classList.remove("underline");
}
}
.bar {
background-color: gray;
padding: 20px;
}
.underline {
border-bottom: 5px solid red;
}
button {
width: 100px;
height: 50px;
background-color: white;
}
button:focus {
outline: none;
}
<div class="bar">
<button id='tab1' class="underline" onclick='switchTab(2)'>Tab 1</button>
<button id='tab2' onclick='switchTab(1)'>Tab 2</button>
</div>
Rather than animating a border I've created an additional element that reacts to the the click events. This allows us to track the position of the "underline" and scale and animate it between buttons when clicked.
This can be modified to accept hover events instead using mouseover instead of click.
let buttons = document.querySelectorAll('button');
buttons.forEach(button => {
button.addEventListener('mouseover', hoverboard); // Hover event
//button.addEventListener('click', hoverboard);
});
function hoverboard(e) {
const board = document.querySelector('.hoverboard');
// - 1 due to the border of the button
let width = this.offsetWidth - 1;
const firstChild = document.querySelector('.bar button:first-child');
const lastChild = document.querySelector('.bar button:last-child');
// - 19 due to padding being 20px on the left and removing 1 for the button's border
let left = this.offsetLeft - 19;
board.style.cssText = 'transform: translateX(' + left + 'px); width: ' + width + 'px;';
}
.bar {
position: relative;
background-color: gray;
padding: 20px;
}
.underline {
border-bottom: 5px solid red;
}
button {
width: 100px;
height: 50px;
background-color: white;
}
button:focus {
outline: none;
}
.hoverboard {
position: absolute;
width: 100px;
height: 3px;
background: red;
transition: transform .25s ease, width .25s ease;
}
<div class="bar">
<button id='tab1'>Tab 1</button>
<button id='tab2' style="width: 65px;">Tab 2</button>
<button>Tab 3</button>
<div class="hoverboard"></div>
</div>
Here ya go. Only the edited classes are here:
.underline:after {
border-bottom: 5px solid red;
animation-name: slideIn;
animation-duration: 1s;
width: 100%;
content: '';
position: absolute;
bottom: 0;
left: 0;
}
#keyframes slideIn {
from {width: 0;}
to {width: 100%;}
}
button{
position: relative;
width: 100px;
height: 50px;
background-color: white;
}
What I did is that I used the abstract after element on the buttons and positioned it absolute to it's relative button. And used css animation.
I have a design which is difficult to describe, I've spent hours googling it and found nothing. The design basically uses a fixed border frame which always stays on the page while the content scrolls through it, with multiple full viewport "slides" stacked vertically. Each slide has a different background image which is fixed for the parallax effect with background-size:cover as well. As you scroll down through the content to the next slide, the border color should change with the content to work with the background image of the next slide. So essentially the side borders will need to be two colors at the same time or have one covering the other. The attached images should make things more clear.
What I have to start with is this content arranged in what I'm calling slides: http://jsfiddle.net/4wtRv/
HTML
<section class="dark" style="background:url('http://www.mccullagh.org/db9/10d-2/new-york-city-at-night.jpg') no-repeat fixed;background-size:cover;">
<div class="brdr_out">
<div class="brdr_in">
<div class="content" style="height:10em;margin-top:-5em;">
<div class="title1">TITLE 1</div>
</div>
</div>
</div>
</section>
<section class="light" style="background:url('http://images.nationalgeographic.com/wpf/media-live/photos/000/004/cache/african-elephant_435_600x450.jpg') no-repeat fixed;background-size:cover;">
<div class="brdr_out">
<div class="brdr_in">
<div class="content" style="height:10em;margin-top:-5em;">
<div class="title2">Title 2</div>
<div class="title3">Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text </div>
</div>
</div>
</div>
</section>
CSS
body, html
{
height: 100%;
}
body
{
font-size: 16px;
margin: 0;
}
.brdr_in, .brdr_out
{
bottom: 0;
height: auto;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.brdr_in
{
margin: .25em;
padding: 1em;
}
.brdr_out
{
margin: 1em;
padding: .25em;
}
.clr1, .dark
{
color: #fff;
}
.clr2, .light
{
color: #000;
}
.dark .brdr_in
{
border: 3px solid #d5d7a1;
}
.dark .brdr_out
{
border: 5px solid #d5d7a1;
}
.light .brdr_in
{
border: 3px solid #000;
}
.light .brdr_out
{
border: 5px solid #000;
}
section
{
height: 100%;
position: relative;
text-align: center;
}
section .content
{
position: absolute;
top: 50%;
width: 100%;
}
.title1, .title2
{
display: inline-block;
letter-spacing: .25em;
line-height: 1.875em;
padding-bottom: .8em;
}
.title2
{
border-bottom: 1px solid #4a4639;
margin-bottom: 3em;
}
But the tricky part is to make it look like this when you scroll:
Also, the text needs to be hidden in the margins outside of the frame.
Your help is much appreciated! This was much more difficult than I anticipated when I told the designer I could do it. Javascript and any of it's libraries are fine. Thanks!
It will be difficult to split the frame into two colors. The easiest way i can imagine is to make two copies of the frame. Give both a fixed position with one top:0, the other bottom:0, and adjust the heights of both as you scroll so they meet precisely in the middle.
Another solution, that's not to your spec, but may look better, is to fade the border color from one slide to the next. You will need a color interpolation function, a list of slide colors in JavaScript, and a scroll handler function that determines which slide is visible and the distance scrolled to the next slide. Something kind of like this (assuming Jquery, and 1000px slides):
$(window).on('scroll', function(){
var slide_distance = window.scrollY / 1000, slide = Math.floor(slide_distance)
var color = color_interp(slide_colors[slide], slide_colors[slide + 1], slide_distance - slide)
$('#border').css('color', color)
})
Here is the rough sample code which solved this. I have to have each border piece be a separate div so it doesn't cover the text, links, etc. To block the text when scrolling above and below the frame I add an element which displays the same background image with a higher z-index so it covers the portion outside the frame.
HTML
<section class="dark" style="background:url('http://www.mccullagh.org/db9/10d-2/new-york-city-at-night.jpg') no-repeat fixed;background-size:cover;">
<div class="content" style="height:10em;margin-top:-5em;">
<div class="title1">TITLE 1</div>
</div>
</section>
<section class="light" style="background:url('http://images.nationalgeographic.com/wpf/media-live/photos/000/004/cache/african-elephant_435_600x450.jpg') no-repeat fixed;background-size:cover;">
<div class="content" style="height:10em;margin-top:-5em;padding:0 20%;width:60%">
<div class="title2">Title 2</div>
<div class="title3">Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text </div>
</div>
</section>
<div id="top" style="background:url('http://www.mccullagh.org/db9/10d-2/new-york-city-at-night.jpg') no-repeat fixed;background-size:cover;display:none;height:24px;position:fixed;top:0;left:0;right:0;z-index:3;"></div>
<div id="bottom" style="background:url('http://images.nationalgeographic.com/wpf/media-live/photos/000/004/cache/african-elephant_435_600x450.jpg') no-repeat fixed;background-size:cover;display:none;height:24px;position:fixed;bottom:0;left:0;right:0;z-index:3;"></div>
<div class="brdr_out">
<div class="dark">
<div class="top"></div>
<div class="right"></div>
<div class="bottom"></div>
<div class="left"></div>
</div>
<div class="light">
<div class="top"></div>
<div class="right"></div>
<div class="bottom"></div>
<div class="left"></div>
</div>
</div>
<div class="brdr_in">
<div class="dark">
<div class="top"></div>
<div class="right"></div>
<div class="bottom"></div>
<div class="left"></div>
</div>
<div class="light">
<div class="top"></div>
<div class="right"></div>
<div class="bottom"></div>
<div class="left"></div>
</div>
</div>
CSS
body, html
{
height: 100%;
}
body
{
font-size: 16px;
margin: 0;
}
.clr1, .dark
{
color: #fff;
}
.clr2, .light
{
color: #000;
}
section
{
height: 100%;
position: relative;
text-align: center;
}
section .content
{
position: absolute;
top: 50%;
width: 100%;
}
.title1, .title2
{
display: inline-block;
letter-spacing: .25em;
line-height: 1.875em;
padding-bottom: .8em;
}
.title2
{
border-bottom: 1px solid #4a4639;
margin-bottom: 3em;
}
/*The Frame Styles*/
.brdr_in .bottom
{
bottom: 24px;
}
.brdr_in .bottom, .brdr_in .top
{
height: 3px;
left: 24px;
right: 24px;
}
.brdr_in .dark div, .brdr_out .dark div
{
background-color: #e4d7b0;
}
.brdr_in div, .brdr_out div
{
position: fixed;
z-index: 4;
}
.brdr_in .left
{
left: 24px;
}
.brdr_in .left, .brdr_in .right
{
bottom: 24px;
top: 24px;
width: 3px;
}
.brdr_in .light div, .brdr_out .light div
{
background-color: #434345;
}
.brdr_in .right
{
right: 24px;
}
.brdr_in .top
{
top: 24px;
}
.brdr_out .bottom
{
bottom: 15px;
}
.brdr_out .bottom, .brdr_out .top
{
height: 5px;
left: 15px;
right: 15px;
}
.brdr_out .left
{
left: 15px;
}
.brdr_out .left, .brdr_out .right
{
bottom: 15px;
top: 15px;
width: 5px;
}
.brdr_out .right
{
right: 15px;
}
.brdr_out .top
{
top: 15px;
}
JS
function borders() {
var viewportHeight = $(window).height();
var scrollY = window.scrollY;
var distance = viewportHeight - scrollY;
//Once we start scrolling, the top border of the next slide needs to be hidden so it doesn't appear over content
if (scrollY >= 0) {
$('.light .top').css('display', 'none');
}
if (scrollY < 27) {
$('.brdr_in .light .bottom').css('display', 'none');
}
else {
$('.brdr_in .light .bottom').css('display', 'block');
}
if (scrollY < 20) {
$('.brdr_out .light .bottom').css('display', 'none');
}
else {
$('.brdr_out .light .bottom').css('display', 'block');
}
var outerTop = distance;
var innerTop = distance;
//We've scrolled enough so that the top of the bottom slide reaches the top of the viewport, need to add top border back in
if (outerTop < 15) {
$('.brdr_out .light .top').css('display', 'block');
outerTop = 15;
}
if (innerTop < 24) {
$('.brdr_in .light .top').css('display', 'block');
innerTop = 24;
}
$('.brdr_out .light .left, .brdr_out .light .right').css('top', outerTop);
$('.brdr_in .light .left, .brdr_in .light .right').css('top', innerTop);
//Add the background image to top/bottom to hide the text as it scrolls under/above it
if ($('#light .brdr_out').css('border-top-width') == '0px') {
$('#top').css('display', 'block');
}
else {
$('#top').css('display', 'none');
}
if (scrollY > 24) {
$('#bottom').css('display', 'block');
}
else {
$('#bottom').css('display', 'none');
}
}
$(window).load(function () {
borders();
});
$(window).scroll(function () {
borders();
});