Is there any way to create a vertical scrolling menu that when you click on a link the whole menu will shift up or down? It's really hard to explain. The best example I could find of what I'm trying to do is the old xbox nxe dashboard.
http://www.youtube.com/watch?v=Q2PyNpbteuU
Where if your links are (HOME - ABOUT - CONTACT) and you click contact; that link will now be centered in the list and about will be on top and home underneath.
Home
About
Contact (than you click on contact)
-
About
Contact
Home (And now it would look like this)
-
Is this possible? Using HTML5? CSS? Javascript?
Here you will find a horizontal and a vertical implementation I did with jquery:
http://codepen.io/rafaelcastrocouto/pen/kuAzl
The HTML code of the vertical menu:
<nav id="vmenu">
<section>
<div class="active"><a>First</a></div>
<div><a>Second</a></div>
<div><a>One more</a></div>
<div><a>XBox</a></div>
<div><a>Menu</a></div>
<div><a>Last</a></div>
</section>
</nav>
The CSS:
#vmenu {
border: 1px solid green;
overflow: hidden;
position: relative;
padding: 5%;
}
#vmenu section {
position: relative;
margin-top: 10%;
transition: top 0.5s;
}
#vmenu section div {
float: left;
display: inline-block;
padding: 0; margin: 0;
transform: scale(1);
transition: transform 0.5s;
}
#vmenu section div.active {
transform: scale(1.2);
}
#vmenu section div a {
text-align: center;
background: #ddd;
box-shadow: 0 0 1em #444;
border-radius: 1em;
display: block;
width: 60%; height: 60%;
padding: 10%;
margin: 10%;
}
#vmenu section div.active a {
background: #ccc;
box-shadow: 0 0 1em;
}
And the JS:
var size = 200;
var count = $('#vmenu section').get(0).children.length;
$('#vmenu').height(2 * size).width(size);
$('#vmenu section').height(size * count);
$('#vmenu section div').height(size).width(size).on('click', function(){
$('#vmenu section div').removeClass('active')
$(this).addClass('active');
var c = this.parentNode.children;
var i = Array.prototype.indexOf.call(c, this);
$('#vmenu section').css('top', i * size * -1);
});
Related
In mobile, I'm trying to create a toggle that appears on top of an image, that when tapped on, makes text appear on top of the image too.
I basically want to recreate how The Guardian newspaper handles the little (i) icon in the bottom right corner on mobile.
And on desktop, the the text is there by default under the image and the (i) icon is gone.
So far I've managed to find a similar solution elsewhere online but it's not quite working right as I need it to.
function toggleText() {
var text = document.getElementById("demo");
if (text.style.display === "none") {
text.style.display = "block";
} else {
text.style.display = "none";
}
}
#blog {
width: 100%;
}
#blog figure {
position: relative;
}
#blog figure figcaption {
display: none;
position: absolute;
bottom: 0;
left: 0;
box-sizing: border-box;
width: 100%;
color: black;
text-align: left;
background: rgba(0, 0, 0, 0.5);
}
#blog figure button {
position: absolute;
bottom: 0;
right: 0;
color: black;
border: 5px solid black;
}
<div id="blog">
<figure>
<img src="https://cdn2.hubspot.net/hubfs/4635813/marble-around-the-world.jpg" alt="A photo of a slab of marble for example">
<figcaption id="demo" style='display: none'>A photo of a slab of marble for example</figcaption>
<button type='button' onclick="toggleText()">(i)</button>
</figure>
</div>
Don't use IDs. Your code should be reusable!
Don't use inline JS on* handlers, use Element.addEventListener() instead
Don't use inline style attributes.
Don't use el.style.display === "something" to check for display styles. Use Element.classList.toggle() instead
This straightforward example uses JavaScript to simply toggle a className "is-active" on the button's parent, the figure Element.
Everything else (icon symbol change, caption animation etc...) is handled purely by CSS:
document.querySelectorAll("figure button").forEach(EL_btn => {
EL_btn.addEventListener("click", () => {
EL_btn.closest("figure").classList.toggle("is-active");
});
});
/* QuickReset */ * {margin: 0; box-sizing: border-box;}
img {
max-width: 100%; /* Never extend images more than available */
}
figure {
position: relative;
overflow: hidden; /* overflow hidden to allow figcaption hide bottom */
}
figure img {
display: block; /* prevent "bottom space" caused by inline elements */
}
figure figcaption {
position: absolute;
width: 100%;
bottom: 0;
padding: 1rem;
padding-right: 4rem; /* Prevent text going under the button icon */
color: #fff;
background: rgba(0, 0, 0, 0.5);
transform: translateY(100%); /* Move down, out of view */
transition: transform 0.3s; /* Add some transition animation */
}
figure.is-active figcaption {
transform: translateY(0%); /* Move into view */
}
figure button {
position: absolute;
width: 2rem;
height: 2rem;
bottom: 0.5rem;
right: 0.5rem;
border-radius: 50%;
color: #fff;
background: rgba(0, 0, 0, 0.5);
border: 0;
cursor: pointer;
}
figure button::before {
content: "\2139"; /* i icon */
}
figure.is-active button::before {
content: "\2A09"; /* x icon */
}
<figure>
<img src="https://cdn2.hubspot.net/hubfs/4635813/marble-around-the-world.jpg" alt="A photo of a slab of marble for example">
<figcaption>A photo of a slab of marble for example</figcaption>
<button type="button"></button>
</figure>
The above will work for any number of such elements on your website without the need to add any more CSS or JS.
I see a couple things that could mess this up, one is the fact that there is nothing to make your image adjust to your mobile screen, more-over there is also margin that is there by default, so I suggest these changes to the CSS:
First I'd set box-sizing to border-box and margin to 0, this should be a regular practice by the way.
*{
box-sizing: border-box;
margin: 0;
}
Then select the image and make it adjust to your page as such
#blog figure img{
height: auto;
width:100%;
}
Finally, for some styling you can add some padding to your blog div to make the image slightly smaller on your screen
#blog {
width: 100%;
padding: 35px;
}
This is the Fiddle for it.
Using slick, I'm trying to achieve a slider that looks like this:
In the above, the center slide is the default selected when slick initiates. Then, when an arrow is clicked, the slick-current class will shift onto a new div and css translate can be used to scale the image.
Here is my current code:
$(function(){
$("#downloadNow__slick").slick({
slidesToShow: 3,
// initialSlide: 2,
centerMode: true,
centerPadding: "53px",
arrows: true,
dots: false,
infinite: true,
cssEase: 'linear',
});
});
.downloadNow {
background: grey;
padding: 60px 0px;
&__wrapper {
position: relative;
}
.downloadNowCard{
background: white;
padding: 100px;
}
.slider {
max-width: 1110px;
margin: 0 auto;
}
.slick-track {
padding-top: 53px;
padding-bottom: 53px;
}
.slick-slide {
text-align: center;
transition: transform 0.3s ease-in-out;
}
.slick-slide.slick-current {
transform: scale(1.35);
position: relative;
z-index: 1;
}
.slick-slide img {
width: 100%;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js" integrity="sha512-XtmMtDEcNz2j7ekrtHvOVR4iwwaD6o/FUJe6+Zq+HgcCsk3kj4uSQQR8weQ2QVj1o0Pk6PwYLohm206ZzNfubg==" crossorigin="anonymous"></script>
<section class="downloadNow">
<div class="downloadNow__wrapper">
<div class="downloadNow__slides" id="downloadNow__slick">
<div class="downloadNowCard">Card 1</div>
<div class="downloadNowCard">Card 2</div>
<div class="downloadNowCard">Card 3</div>
</div>
</div>
</section>
Current issues:
Cannot achieve row layout (flex on wrapper not playing nicely)
If I uncomment out initialSlide: 2, the slider breaks. I'm trying to get the center slide as the active slide with this.
Slick not changing slide
Here you go...
It took me some time to figure it out because of some strange behavior of the slick slider.
To begin with, the slick slider didn't work with newer versions of jQuery. After a lot of trial and error (and searching through older answers here on SO), I figured out it works only with the older version of jQuery for some strange reason. This is the main reason it didn't work for you too. I used older version of jQuery than you did and it still didn't work. It works with v1.12.3 (it might also work with some newer versions of jQuery, but I didn't test which is the latest compatible version).
To achieve a 3D effect, you can use box-shadow: 0.1vw 0.8vw 1vw rgba(0, 0, 0, 0.35);. With the current values (i.e. 0.1vw 0.8vw 1vw), you get a blurry shadow at the bottom and a little bit on the right side. The first value (i.e. 0.1vw) defines a horizontal offset. The second value (i.e. 0.8vw) defines a vertical offset. The third value (i.e. 1vw) defines blur radius so that the shadow is "smooth" (try to remove 1vw and you'll get the point).
Also, I managed to solve all your issues:
the row layout works,
the middle card is always an "active card" (a little bit bigger box, a little bit bigger font) and
arrows work too (but you have to use z-index: 100; to push them to the front).
$(document).on('ready', function() {
$('.slider').slick({
dots: true,
centerMode: true,
infinite: true,
centerPadding: '60px',
slidesToShow: 3,
speed: 400
});
});
html {
height: 100%;
overflow-x: hidden;
}
body {
background: #F8F8F8;
height: 100%;
margin: 0;
}
.slider {
width: 90%;
left: 5%;
}
h3 {
background: #fff;
color: #202020;
font-size: 3.5vw;
line-height: 23vh;
margin: 6.5vw;
margin: 6.5vw;
padding: 1vw;
position: relative;
text-align: center;
box-shadow: 0.1vw 0.8vw 1vw rgba(0, 0, 0, 0.35);
}
.slick-center {
transition: 0.2s ease-in-out;
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
transform: scale(1.3);
}
.slick-center h3 {
font-size: 4vw;
}
.slick-prev,
.slick-next {
z-index: 100 !important;
}
.slick-prev:before {
transition: 0.2s ease-in-out;
color: #303030 !important;
font-size: 2vw !important;
margin-right: -10vw;
}
.slick-next:before {
transition: 0.2s ease-in-out;
color: #303030 !important;
font-size: 2vw !important;
margin-left: -10vw;
}
.slick-dots li button:before {
transition: 0.2s ease-in-out;
font-size: 0.7vw !important;
}
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Document</title>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.js'></script>
<link href='https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css' rel='stylesheet' />
<link href='https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css' rel='stylesheet' />
</head>
<body>
<div class='slider'>
<div>
<h3>1</h3>
</div>
<div>
<h3>2</h3>
</div>
<div>
<h3>3</h3>
</div>
<div>
<h3>4</h3>
</div>
<div>
<h3>5</h3>
</div>
<div>
<h3>6</h3>
</div>
</div>
</body>
</html>
For a smooth 3D Effect you need to play with shadows, by increasing/decreasing the shadow you get a visual illusion of a popping out/in box, for the animation effects play with css transition use only transform for better performance.
You don't need external libraries to achieve this, the example below is a pure HTML/CSS/Javascript slider.
Edit: add some explanatory comments to the example.
// Button click events
const buttons = document.querySelectorAll('b')
buttons.forEach((button, i) =>
button.onclick = () => slide(i)
)
// Slide function
function slide(right) {
// Get the SLider elements
const container = document.querySelector('div');
const slider = document.querySelector('ul');
const items = document.querySelectorAll('li');
const featured = document.querySelector('.featured');
// Get the featured item
const featuredIndex = [...items].indexOf(featured);
// Set the move if not right then left
const move = right ? 1 : -1;
// Check the slider limits
// if right and index 0: do nothing
// if left and last item: do nothing
if((!right && !featuredIndex)
|| (right && featuredIndex === items.length -1))
return;
// Get the next item element
const nextIndex = featuredIndex + move;
const nextItem = items[nextIndex]
// Remove "featured" class from last item
featured.classList.remove("featured");
// Add "featured" class the next item
nextItem.classList.add("featured");
// Get the container size
const { width: containerSize } = container.getBoundingClientRect();
// Get the next item size and position
const itemSize = containerSize / 3; // Display only 3 items
const itemPosition = itemSize * (nextIndex + 1); // Current position
// Compute the slider next position
const position = containerSize - itemPosition - itemSize;
// Move the slider on its X axis using css transform and transitionX
slider.style.transform = `translateX(${position}px)`
}
body{
background: #ddd;
}
/* Some csss reboot */
ul, li{
margin: 0;
padding: 0;
list-style-type: none;
}
/* Container */
div {
position: relative;
width: 100%;
overflow-x: hidden;
transform: transitionX(0);
}
/* Preview/Next Buttons */
b {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0.5em;
width: 20px;
height: 20x;
line-height: 20px;
font-size: 1.2em;
color: #666;
cursor: pointer;
transform: translateY(-50%) scale(1);
transition: transform .1s ease-out;
}
b:last-child {
left: unset;
right: 0.5em;
}
/* Button animation on hover */
b:hover{
transform: translateY(-50%) scale(1.3);
}
/* Slider */
ul {
position: relative;
padding: 2em 0;
display: flex;
width: 100%;
height: 150px;
transform: translateX(0);
transition: transform .3s ease-out;
}
/* Items */
li {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
font-size: 4em;
font-weight: 500;
font-family: arial;
color: #555;
min-width: calc(100% / 3);
background: #fff;
border-radius: .5em;
/* Animation */
transform: translateX(10%) scale(0.5);
box-shadow: 0 2px 2px 0 rgb(0 0 0 / 14%), 0 3px 1px -2px rgb(0 0 0 / 12%), 0 1px 5px 0 rgb(0 0 0 / 20%);
transition: all .3s ease-out;
}
/* Featured Item */
.featured {
z-index: 1;
/* Animation */
transform: scale(1);
box-shadow: 0 16px 24px 2px rgb(0 0 0 / 14%), 0 6px 30px 5px rgb(0 0 0 / 12%), 0 8px 10px -7px rgb(0 0 0 / 20%)
}
.featured + li {
/* Offset the next element under the selected element */
transform: scale(.5) translateX(-25%) !important;
}
<div> <!-- Container -->
<ul> <!-- Slider -->
<li>1</li> <!-- Ithems -->
<li class="featured">2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
<b>ᐸ</b> <!-- Preview button -->
<b>ᐳ</b> <!-- Next button -->
</div>
The transition of my header and navigation finish in the desired places and have the correct look I am going for, however, the transition is jumpy, almost as if something is trying to restrict the translation from happening. Any advice on how I can go about fixing this erratic transition behavior?
With a similar problem, the padding of the initial element style was the problem (before JavaScript changed the style class.)
I expect the transition to be smooth and seamless, not fighting to transition into it's proper end place.
Thanks again for any help.
PS: I'm not sure how to reference the JS file at the bottom of the HTML code to allow the JS code snippet to change the html elements properly. The CSS which is supposed to make the 'headerLogoBackground' sticky at the very top of the window and change is not being applied which is needed to answer the question. However, I don't have this problem on my local machine - sorry guys, working on fixing this in the stackoverflow code snippet editor:/
// JavaScript
window.onscroll = function() {
headerSlide_and_change();
}; // end window.onscroll
function headerSlide_and_change() {
if (document.body.scollTop > 30 || document.documentElement.scrollTop > 30) {
// vars
var headerLogoBackground = document.getElementById("headerLogoBackground");
var headerLogo = document.getElementById("headerLogo");
headerLogoBackground.classList.remove("headerLogoBackground"); // header background
headerLogoBackground.classList.add("headerLogoBackGroundTransform");
headerLogo.classList.add("headerMove"); //header title slide
} else {
/*---header background---*/
var headerLogoBackground = document.getElementById("headerLogoBackground");
headerLogoBackground.classList.remove("headerLogoBackGroundTransform");
headerLogoBackground.classList.add("headerLogoBackground");
/*---header---*/
var headerLogo = document.getElementById("headerLogo");
headerLogo.classList.remove("headerMove");
headerLogo.classList.add("headerLogo");
} // end else
} // end navChangeOnScrollUp
html,
body {
margin: 0;
height: 100vw;
}
html {
scroll-behavior: smooth;
}
body {
background-color: #796651;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
// Initial CSS
// header
.headerLogoBackground {
background-color: none;
transition: background-color .2s linear;
}
.headerLogo {
text-align: center;
transition: all .2s;
}
.headerLogo a {
font-family: 'Squada One', cursive;
font-size: 40px;
text-decoration: none;
color: #d8cfc3;
}
.headerLogo a:focus {
outline: none;
color: darkslategrey;
}
// navigation bar
.navRaise {
position: -webkit-sticky;
position: static;
z-index: 2;
}
nav {
text-align: center;
font-weight: bold;
width: 100%;
margin-bottom: 8%;
}
.navUlStyle {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: rgba(0, 0, 0, 0.26);
height: 45px;
line-height: 45px;
}
nav li {
float: left;
width: 8%;
padding-left: 12.5%;
padding-right: 12.5%;
}
nav a {
font-family: 'Squada One', cursive;
font-size: 20px;
text-decoration: none;
color: #d8cfc3;
display: block;
text-align: center;
position: relative;
z-index: 3;
}
// JavaScript CSS Classes used to change initial CSS
.headerLogoBackGroundTransform {
/* to replace 'headerLogoBackground' */
position: sticky;
top: 0;
background-color: white !important;
transition: all .2s linear;
z-index: 10;
}
.headerMove { /* to replace 'headerLogo' */
margin-block-start: 0;
transform: translate(-10%, 0px) !important;
transition: transform .2s;
display: inline-block;
width: 30%;
float: left;
}
<html>
<head>
<title>...</title>
<link href="https://fonts.googleapis.com/css?family=Pinyon+Script|Squada+One&display=swap" rel="stylesheet">
</head>
<body id="body">
<div id="Welcome"><!--Wrapper div-->
<!-------------------------Top Section------------------------->
<header id="headerLogoBackground" class="headerLogoBackground">
<!--header-->
<h1 id="headerLogo" class="headerLogo"><a id="headerLogoLink" href="index.html">Lorem Dolo</a></h1>
<!--navigation-->
<nav id="navRaise" class="navRaise">
<ul id="navUl" class="navUlStyle">
<li><a id="topNav" title="Top" href="#Welcome"> Top </a></li>
<li><a id="servicesNav" title="Services" href="#Services"> Services </a></li>
<li><a id="contactNav" title="Contact" href="#Contact"> Contact </a></li>
</ul>
</nav>
</header>
</div><!-- end wrapper div -->
<!------------------------JavaScript Documents--------------------------->
<!-- I don't think path to external JavaScript file is relevant for stackoverflow code snippet -->
</body>
</html>
I am trying to smooth out this custom animation.
See Working Animation Here.
The problem I am having is that when the city name revolves up and replaces the old one, the text-align center causes the h2 text to re-align center in one frame. I want to smooth out this transition so it eases into the align center instead of just jumping to it.
I hope that explanation helps. Here is my code.
HTML
<div class="coverage">
<h2>Kellin has service in <span class="flip"></span></h2>
<ul class="coverage_list">
<li>Larkspur</li>
<li>Castle Rock</li>
<li>Monument</li>
<li>Palmer Lake</li>
<li>Colorado Springs</li>
<li>Pueblo</li>
<li>Peyton</li>
<li>Falcon</li>
<li>Calhan</li>
<li>Franktown</li>
<li>Elizabeth</li>
<li>Elbert</li>
<li>Glenwood Springs</li>
<li>Rifle</li>
<li>Silt</li>
<li>El Jebel</li>
<li>Carbondale</li>
<li>New Castle</li>
<li>Parachute</li>
<li>Battlement</li>
</ul>
</div><!-- end .coverage -->
CSS
/* Coverage Banner */
.coverage{
border: 2px solid #333;
width: 100%;
}
.coverage ul.coverage_list{
display: none;
}
.coverage h2{
font-size: 2em;
font-weight: 700;
padding: 0px;
margin:0px;
overflow: hidden;
display: block;
text-align: center;
white-space: nowrap;
}
.coverage h2 .flip{
display: inline-block;
position: relative;
padding: 0px;
margin: 0px;
}
.coverage h2 .flip .current{
position: relative;
left:0;
display: inline-block;
text-align: left;
width: 100%;
padding: 0px;
margin: 0px;
}
.coverage h2 .flip .newcity{
position: absolute;
display: inline-block;
text-align: left;
white-space: nowrap;
left: 0;
padding: 0px;
margin: 0px;
}
JS / Jquery
var coverageVars = {
index : 1,
count : 0,
flipTime : 500
}
// .current = Current City, position relative
// .newcity = New City, position absolute;
$(document).ready(function(){
// Load First City Into H2 Display
var firstCity = $('ul.coverage_list li:nth-child(1)').html();
$('.coverage h2 .flip').append('<span class="current">'+firstCity+'!</span>');
// Get Count
coverageVars.count = $('ul.coverage_list').children('li').length;
var flipTimer = setInterval(function(){
// Increase Counter
if( coverageVars.index < coverageVars.count ){
coverageVars.index += 1;
} else {
coverageVars.index = 1;
}
// Get City Names
var currentCity = $('.coverage h2 .flip .current').html();
var newCity = $('ul.coverage_list li:nth-child('+coverageVars.index+')').html();
// Append newcity span to flip element
$('.coverage h2 .flip').append('<span class="newcity" style="top:50px;">'+newCity+'!</span>');
$('.coverage h2 .flip .current').animate({top:'-50px'}, coverageVars.flipTime, function(){
$(this).remove();
});
$('.coverage h2 .flip .newcity').animate({top:0}, coverageVars.flipTime, function(){
console.log('done!');
$(this).removeClass('newcity').addClass('current');
});
}, 1500);
});
I figure i would have to align with margins and then put a css transition on the margins but I can't figure it out. Thanks for the help.
An other idea:
Get the width of your <h2> (without .flip)
Get the width of the next <li> that will be appended. For this .coverage_list can't be set to display: none, but you can set the height: 0 and overflow to hidden.
Animate your <h2> to the new width (<h2> + <li>). Maybe with 1-2px more, due to browser rendering
Repeat steps #2 and #3 and always animate the width before appending
If you see the code sample I have shared, you can see the overlay going outside the box. I traced the issue down to the transition attribute.
I want to remove the content outside of the div. Overflow isn't working as it is supposed to. (removing transition works, but I would like to keep it if possible)
Any help is appreciated
Codepen Link
CODE
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Actually it is the border-radius that is not getting respected when the transition is happening. This is because of creation of compositing layers for accelerated rendering and can be explained by having a look at the following articles:
HTML5 Rocks - Accelerated Rendering in Chrome
GPU Accelerated Compositing in Chrome.
Why does the issue not happen when transition is disabled?
When styles change but none of the criteria that necessitates the creation of a compositing layer is satisfied (that is, no animation or transition or 3D transform etc):
There is no compositing layer and so the whole area seems to get repainted at every change. Since a full repaint happens there is no issue.
View the below snippet (in full screen mode) after enabling "Show paint rects" and "Show composited layer borders" from Dev tools and observe the following:
No areas with an orange border (compositing layer) are created.
Every time the styles are modified by setting the focus on one of the a tags, the whole area gets repainted (a red or green blinking area).
.outer {
position: relative;
height: 100px;
width: 100px;
margin-top: 50px;
border: 1px solid red;
overflow: hidden;
}
.border-radius {
border-radius: 50px;
}
.inner {
width: 50px;
height: 50px;
background-color: gray;
opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
transform: translateX(50px);
height: 51px;
opacity: 0.5;
}
<a href='#'>Test</a>
<div class='outer border-radius'>
<div class='inner'>I am a strange root.
</div>
</div>
Why does adding a transition create a problem?
Initial rendering has no compositing layer because there is no transition yet on the element. View the below snippet and note how when the snippet is run a paint (red or green blinking area) happens but no compositing layer (area with orange border) is created.
When transition starts, Chrome splits them into different compositing layers when some properties like opacity, transform etc are being transitioned. Notice how two areas with orange borders are displayed as soon as the focus is set on one of the anchor tags. These are the compositing layers that got created.
The layer splitting is happening for accelerated rendering. As mentioned in the HTML5 Rocks article, the opacity and transform changes are applied by changing the attributes of the compositing layer and no repainting occurs.
At the end of the transition, a repaint happens to merge all the layers back into a single layer because compositing layers are no longer applicable (based on criteria for creation of layers).
.outer {
position: relative;
height: 100px;
width: 100px;
margin-top: 50px;
border: 1px solid red;
overflow: hidden;
}
.border-radius {
border-radius: 50px;
}
.inner {
width: 50px;
height: 50px;
background-color: gray;
transition: all 1s 5s;
/*transition: height 1s 5s; /* uncomment this to see how other properties don't create a compositing layer */
opacity: 0.75;
}
a:focus + .outer.border-radius > .inner {
transform: translateX(50px);
opacity: 0.5;
/*height: 60px; */
}
<a href='#'>Test</a>
<div class='outer border-radius'>
<div class='inner'>I am a strange root.
</div>
</div>
This illustrates that when the layers are merged back and full repaint happens, the border-radius on the parent also gets applied and respected. However, during transition only the compositing layer's properties are changed, so the layer seems to become unaware of the properties of other layers and thus doesn't respect the border-radius of the parent.
I would assume this to be because of the way rendering of layers work. Each layer is a software bitmap and so it kind of becomes equivalent to having a circular image and then placing a div on top of it. That would obviously not result in any clipping of content.
The comment in this bug thread also seems to confirm that a repaint happens when a separate layer is no longer required.
We want to repaint if "gets own layer" is going to change
Note: Though they are Chrome specific, I think the behavior should be similar in others also.
What is the solution?
The solution seems to be to create a separate stacking context for the parent (.qs-timer) element. Creating a separate stacking context seems to result in a separate compositing layer being created for the parent and this solves the issue.
As mentioned by BoltClock in this answer, any one of the following options would create a separate stacking context for the parent and doing one of them seems to resolve the issue.
Setting a z-index on the parent .qs-timer to anything other than auto.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
z-index: 1; /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Setting opacity to anything less than 1. I have used 0.99 in the below snippet as it doesn't cause any visual difference.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
opacity: 0.99; /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
Adding a transform to the element. I have used translateZ(0px) in the below snippet as this also doesn't create any visual difference.
var timer = setInterval(function() {
document.querySelector(".qs-timer-overlay").style.opacity = (document.querySelector(".qs-timer-overlay").style.opacity * 1) + 0.1;
if (document.querySelector(".qs-timer-overlay").style.opacity * 1 == 1) {
clearInterval(timer);
}
}, 1000);
.qs-main-header .qs-timer {
padding: 13px 10px;
min-width: 130px;
text-align: center;
display: inline-block;
background-color: #dd8b3a;
color: #FFF;
font-size: 20px;
border-radius: 50px;
text-transform: uppercase;
float: right;
cursor: pointer;
position: relative;
overflow: hidden;
transform: translateZ(0px) /* creates a separate stacking context */
}
.qs-main-header .qs-timer-overlay {
z-index: 1;
width: 10%;
max-width: 100%;
position: absolute;
height: 100%;
top: 0;
left: 0;
background-color: #c7543e;
opacity: 0.0;
/* border-radius: 50px 50px 0px 50px; */
}
.qs-main-header .qs-timer-content {
z-index: 2;
position: relative;
}
.scale-transition {
-webkit-transition: all 1s;
transition: all 1s;
}
<div class="qs-main-header">
<div class="qs-timer scale-transition ng-hide" ng-show="visibility.timer">
<div class="scale-transition qs-timer-overlay"></div>
<div class="qs-timer-content ng-binding">0 <span class="ng-binding">Sec(s)</span>
</div>
</div>
</div>
The first two approaches are more preferable than the third because the third one works only on a browser that supports CSS transforms.
Yes, adding opacity: 0.99; to .qs-timer issue will fixed.
When opacity: 1 OR NOT define:
In this special case, there is no transparency involved so that gfx could avoid doing the expensive things.
In case Opacity: 0.99:
nsIFrame::HasOpacity() decides that there is an opacity, so gfx include valuable things. ( likes opacity with border-radius)
For more help Special case opacity:0.99 to treat it as opacity:1 for graphics , This ticket is not providing the opinion of our actual goal, but giving the idea about what is happening inside of CSS.