I have no idea how to ask this question, but here's problem:
I have this kind of search bar ^ and when you double click on it it shrinks and input is disappearing (display: none). But when it disappears
And I-beam cursor shows. So I can't click or double click on it anymore (for some reason)
I tried input.blur(); but because it's not focused it didn't work. I have no clue what to google to fixed. I tried some but there weren't any related answers.
HTML:
<div class="search-field">
<input placeholder="Search" type="text">
<img src="search.svg" title="Double Click to toggle">
<img src="grip-lines.svg" alt="||" id="drag">
</div>
I use keyframes so I will how CSS too: (Edit: I will put more about css)
.search-field {
position: absolute;
width: 385px;
width: 20%;
background-color: #FFFFFF;
top: 15px;
left: 8%;
display: flex;
align-items: center;
justify-content: space-evenly;
padding: 5px;
border-radius: 10px;
border: 1px black solid;
}
input {
height: 80%;
width: 300px;
outline: none;
transition: .3s;
border-radius: 10px;
border: 1px black solid;
padding: 3px 5px;
background-color: #E4E9F7;
}
#keyframes openSearch {
0% {
width: 77px;
}
100% {
width: 385px;
}
}
#keyframes closeSearch {
0% {
width: 385px;
}
100% {
width: 77px;
}
}
.openedSearch {
animation: openSearch 0.5s both;
}
.closedSearch {
animation: closeSearch 0.5s both;
}
JS:
let searchState = true
let toggleSearchState = () => {
searchState = !searchState
if (searchState == true) {
searchField.className = 'search-field openedSearch'
input.style.display = 'block'
} else {
searchField.className = 'search-field closedSearch'
input.style.display = 'none'
}
}
searchBtn.addEventListener('dblclick', toggleSearchState)
Since everything seems to be working fine on this fiddle. The only thing left (that I couldn't test) is the SVG images and any code you attached to them.
As the id of the pipe image is drag, I am assuming you have attached a drag event on it. Also, if the size of the image is not set you may be overlapping, covering the entire search-field when the width is set too 77px.
An easy solution would be to set the height and width of the img tag with the draggable event, so you can be sure it is not overlapping.
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.
I have a small carousel that plays automatically on page load, using HTML, CSS and JavaScript and definitely no jQuery.
To add a pause/play option there is a span with role="checkbox" followed by a label.
The label itself is hidden and has no content. The span has two pseudo elements. On first showing, the pseudo element shows the ⏸ character, controlled by a CSS ::after class. When clicked, the span has the class "is-clicked" added, at which point the ▶ character is displayed, controlled by another ::after class
It is focusable and can be activated with the keyboard by hitting the Enter key, but when I check with Lighthouse, I keep getting the "Focusable elements should have interactive semantics".
Why is this?
Here is the code:
/* detect keyboard users */
function handleFirstTab(e) {
if (e.key === 'Tab') { // the 'I am a keyboard user' key
document.body.classList.add('user-is-tabbing');
window.removeEventListener('keydown', handleFirstTab);
}
}
let checkboxEl = document.getElementById('checkbox');
let labelEl = document.getElementById('checkboxLabel');
labelEl.onclick = function handleLabelClick() {
checkboxEl.focus();
toggleCheckbox();
}
function toggleCheckbox() {
let isChecked = checkboxEl.classList.contains('is-checked');
checkboxEl.classList.toggle('is-checked', !isChecked);
checkboxEl.setAttribute('aria-checked', !isChecked);
}
checkboxEl.onclick = function handleClick() {
toggleCheckbox();
}
checkboxEl.onkeypress = function handleKeyPress(event) {
let isEnterOrSpace = event.keyCode === 32 || event.keyCode === 13;
if(isEnterOrSpace) {
toggleCheckbox();
}
}
.link {
height: auto;
border: 1px solid #000;
margin-bottom: 1rem;
width: 80%;
display: block;
}
#carousel-checkbox {
margin-bottom: 1rem;
height: 50px;
width: 100px;
display: inline-block;
}
#carousel-checkbox input {
display: none;
}
#carousel-checkbox label {
display: inline-block;
vertical-align: middle;
}
#carousel-checkbox #checkbox {
position: relative;
top: 0;
left: 30px;
padding: 0.5rem 1rem;
background: rgba(255,255,255, 0.5);
}
#carousel-checkbox #checkbox:hover {
cursor: pointer;
}
#carousel-checkbox #checkbox:focus {
border: 1px dotted var(--medium-grey);
}
#carousel-checkbox #checkbox::after {
content: "⏸";
font-size: 1.5rem;
color: var(--theme-dark);
}
#carousel-checkbox #checkbox.is-checked::after {
content: "▶";
}
<div class="link">A bit of text with a dummy link to demonstrate the keyboard tabbing navigation. </div>
<div id="carousel-checkbox"><span id="checkbox" tabindex="0" role="checkbox" aria-checked="false" aria-labelledby="checkboxLabel"></span><label id="checkboxLabel"></label></div>
<div class="link">Another link to another dummy link</div>
Why is this? Is it because the pseudo elements don't have a name attribute or something like that?
I have tried a different way, by dropping the pseudo elements and trying to change the span innerHTML depending on whether the class 'is-clicked' exists or not, but although I can get the pause character to display initially, it won't change the innerHTML to the play character when the span is clicked again.
Short Answer
This is a warning rather than an error, it is telling you to check that the item actually is interactive.
Now you have got the interactivity on the element so you can ignore that issue.
Long answer
Why not just use a <input type="checkbox"> and save yourself an awful lot of extra work?
You can hide a checkbox with a visually hidden class.
This then allows you to do the same trick with a pseudo element as the visual representation of the state.
I have made several changes to your example that mean you don't have to worry about capturing keypresses etc. and can just use a click handler so your JS is far simpler.
Notice the trick with the label where I add some visually hidden text within it so the label is still visible (so we can still use psuedo elements!).
I then use #checkbox1 ~ label to access the label with CSS so we can change the state.
The final thing to notice is how I changed the content property slightly. This is because some screen readers will try and read out pseudo elements so I added alt text that was blank. Support isn't great at just over 70%, but it is worth adding for browsers that do support it.
Example
The below hopefully illustrates a way of achieving what you want with a checkbox.
There may be a few errors as I just adapted your code so please do not just copy and paste!
note: a checkbox should not work with Enter, only with Space. If you want it to work with both it should instead be a toggle switch etc. so that would be a completely different pattern.
let checkboxEl = document.getElementById('checkbox1');
let labelEl = document.querySelector('#checkboxLabel');
function toggleCheckbox() {
let isChecked = checkboxEl.classList.contains('is-checked');
checkboxEl.classList.toggle('is-checked', !isChecked);
checkboxEl.setAttribute('aria-checked', !isChecked);
}
checkboxEl.onclick = function handleClick() {
toggleCheckbox();
}
.link {
height: auto;
border: 1px solid #000;
margin-bottom: 1rem;
width: 80%;
display: block;
}
#carousel-checkbox {
margin-bottom: 1rem;
height: 50px;
width: 100px;
display: inline-block;
}
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
#carousel-checkbox label {
display: inline-block;
vertical-align: middle;
}
#carousel-checkbox #checkbox1 {
position: relative;
top: 0;
left: 30px;
padding: 0.5rem 1rem;
background: rgba(255,255,255, 0.5);
}
#carousel-checkbox #checkbox1 ~label:hover {
cursor: pointer;
}
#carousel-checkbox #checkbox1:focus ~ label {
border: 1px dotted #333;
}
#carousel-checkbox #checkbox1 ~label::after {
content: "⏸" / "";
font-size: 1.5rem;
color: #000;
}
#carousel-checkbox #checkbox1.is-checked ~label::after {
content: "▶" / "";
}
<div class="link">A bit of text with a dummy link to demonstrate the keyboard tabbing navigation. </div>
<div id="carousel-checkbox">
<input type="checkbox" id="checkbox1" class="visually-hidden">
<label for="checkbox1" id="checkboxLabel">
<span class="visually-hidden">Pause animations</span>
</label>
</div>
<div class="link">Another link to another dummy link</div>
In the end, I gave up on using a checkbox, due to the difficulties with iPad/iOS not responding to checkbox events. Whilst it worked in codepen on iOS it wouldn't work on the actual site. So I switched to a button.
Here is the code, which is fully accessible with no 'interactive semantics' warnings, shown with some dummy slides. The animation is based on having only three slides. If you wanted more or less, then the timings would have to be adjusted accordingly. All I need now is to style the pause button.
let element = document.getElementById("pause");
function toggleButton() {
element.classList.toggle("paused");
if (element.innerHTML === "⏸") {
element.innerHTML = "▶";
}
else {
element.innerHTML = "⏸";
}
}
element.onclick = function handleClick() {
toggleButton();
}
#carousel {
height: auto;
max-width: 1040px;
position: relative;
margin: 4rem auto 0;
}
#carousel > * {
animation: 12s autoplay6 infinite linear;
position: absolute;
top: 0;
left: 0;
opacity: 0.0;
}
#carousel .one {
position: relative;
}
.homeSlides {
height: 150px;
width: 400px;
background-color: #ff0000;
}
.homeSlides.two {
background-color: #0fff00;
}
.homeSlides.three {
background-color: #e7e7e7;
}
#keyframes autoplay6 {
0% {opacity: 0.0}
4% {opacity: 1.0}
33.33% {opacity: 1.0}
37.33% {opacity: 0.0}
100% {opacity: 0.0}
}
#carousel > *:nth-child(1) {
animation-delay: 0s;
}
#carousel > *:nth-child(2) {
animation-delay: 4s;
}
#carousel > *:nth-child(3) {
animation-delay: 8s;
}
#carousel-button {
position: relative;
height: 100%;
width: auto;
}
#carousel-button button {
position: absolute;
top: -3.5rem;
right: 5rem;
padding: 0 0.5rem 0.25rem;;
background: #fff;
z-index: 98;
font-size: 2rem;
cursor: pointer;
}
body.user-is-tabbing #carousel-button button:focus {
outline: 1px dotted #333;
}
body:not(.user-is-tabbing) #carousel-button button:focus {
outline: none;
}
#carousel-button button:hover {
cursor: pointer;
}
#carousel-button ~ #carousel * {
animation-play-state: running;
}
#carousel-button button.paused ~ #carousel * {
animation-play-state: paused;
}
<div id="carousel-button"><button id="pause" class="">⏸</button>
<div id="carousel">
<div class="homeSlides one">This is div one</div>
<div class="homeSlides two">This is div two</div>
<div class="homeSlides three">This is div three</div>
</div>
</div>
Result looked for:
MODE 1: when the window is large (say >465px) the TOC items is displayed to the left of the content page
MODE 2: when the window's width gets smaller than 465px, reduce the width of the TOC item using transition
MODE 3: when the window's width gets greater than 465px, increase the width of the TOC item using a transition
finally, when the window's width < 465px and that the TOC is therefore hidden as a result of the mechanism described above, show some text on top that users can click on. When they click on this text, display the TOC item as an overlay. When you click on this text again, hide the TOC item as an overlay.
How to see the problem I try to get rid of:
increase the window to a large width and then back to small width. See the transitions when you go from one to the other. This is good.
make the window small so that the "Show Table of Content" text shows up. Click on the text. The TOC is displayed as an overlay. This is good. Then click again, to HIDE the TOC as an overlay. The cyan TOC disappears, but a transition is played right after. That's the problem. I want to get rid of this transition.
This behavior doesn't make sense to me, because the media query specifies that when the window < 465px the width of the TOC is 0. So why it is reset to 150px is a mystery to me. But the most important for me is, how do I get rid of this unwanted transition when the TOC as an overlay is removed (when the the 'overlay' class is toggled (off)?
function showMenuAsOverlay(caller) {
var node = document.getElementById("toc");
node.classList.toggle('overlay');
if (node.classList.contains('overlay'))
caller.innerHTML = "Hide Table of Content";
else
caller.innerHTML = "Show Table of Content";
}
.wrapper {
display: flex;
flex-direction: row;
border: 3px solid black;
z-index: -1;
position: relative;
}
.container-left {}
#toc {
border: 1px solid green;
flex: 0 0 auto;
white-space: pre;
z-index: -1;
width: 150px;
background-color: red;
transition: width 1s ease-out;
box-sizing: border-box;
}
.container-right {
display: flex;
border: 1px solid red;
flex 1 1 auto;
max-width: 400px;
background-color: white;
z-index:-1;
box-sizing: border-box;
}
.myicon {
cursor: pointer;
visibility: hidden;
}
#media
screen and (max-width: 465px) {
#toc {
width: 0;
background-color: purple;
transition: width 1s ease-out;
}
#toc.overlay {
z-index: 999;
position: absolute;
left: 0px;
background-color: cyan;
bottom: 0;
top: 0;
width: 150px;
transition: left 1s ease-out;
}
.myicon {
visibility: visible;
}
}
<body>
<div onclick="showMenuAsOverlay(this)" class="myicon" id="myicon">Show Table of Content</div>
<div class="wrapper">
<div class="container-left" id="toc" data-state="0">This is some text in the TOC</div>
<div class="container-right">
This is some content this is some content this is some more content, and this is content again and again.
</div>
</div>
</body>
The transition is happening when <div id="toc"> loses the class overlay.
That means you go from applying this rule:
#toc.overlay {
z-index: 999;
position: absolute;
left: 0px;
background-color: cyan;
bottom: 0;
top: 0;
width: 150px;
transition: left 1s ease-out;
}
to applying this rule:
#toc {
width: 0;
background-color: purple;
transition: width 1s ease-out;
}
This makes it clear why the transition is happening. You're going from width: 150px to width: 0 with this transition applied from #toc: width 1s ease-out;
Also, you've got this applied without a media query:
#toc {
border: 1px solid green;
flex: 0 0 auto;
white-space: pre;
z-index: -1;
width: 150px;
background-color: red;
transition: width 1s ease-out;
box-sizing: border-box;
}
This means the transition applies whatever the screen size. I don't think that's what you want. Put a media query around that block to only apply when you really want it to.
I've been trying to create the following animation:
(1) I have a button consisting of a white 100x100 box on top of a black 100x100 box.
(2) Clicking on the button makes the white box disappear into the black box.
(see the result here)
// html
<div class="button">
<div class="white u-on-top"></div>
<div class="black"></div>
</div>
// css
.button {
width: 100px;
height: 100px;
margin: 10px;
position: relative;
display: inline-block;
.white {
width: 100%;
height: 100%;
background: #fff;
border: 1px solid #000;
position: absolute;
transition: .5s;
}
.black {
width: 100%;
height: 100%;
background: #000;
border: 1px solid #000;
}
}
.u-on-top {
z-index: 1;
}
.u-at-bottom {
z-index: -1;
}
// javascript
var btn = document.querySelector(".button");
var btnState = false;
btn.addEventListener("click", () => {
var btnw = btn.querySelector(".white");
if (!btnState) {
btnw.style.transform = "scale(0)";
btnState = true;
} else {
btnw.style.transform = "scale(1)";
btnState = false;
}
})
(3) What I've unsuccessfully been trying to do is to also make the black box disappear into the white box when clicked on.
So:
clicking on the white box makes it disappear into the black box
clicking on the black box makes it disappear into the white box
clicking on the white box makes it disappear into the black box
And so on...
My idea was to obtain the effect by modifying the z-index of the boxes when clicked on, using the utility classes u-on-top and u-at-bottom (eg the black box is brought to the top after the white box disappears), but I got some weird results.
You can try to adjust some transition, the trick is to add a delay to z-index so it changes after the scale effect. I have also change the JS code and reduced the CSS:
var btnW = document.querySelector(".white");
var btnB = document.querySelector(".black");
var btnState = false;
btnW.addEventListener("click", () => {
btnB.classList.remove('hide');
btnW.classList.add('hide');
btnW.classList.remove('u-on-top');
btnB.classList.add('u-on-top');
})
btnB.addEventListener("click", () => {
btnW.classList.remove('hide');
btnB.classList.add('hide');
btnB.classList.remove('u-on-top');
btnW.classList.add('u-on-top');
})
.button {
width: 100px;
height: 100px;
margin: 10px;
position: relative;
display: inline-block;
cursor: pointer;
}
.button>div {
width: 100%;
height: 100%;
border: 1px solid #000;
position: absolute;
transition: transform 0s, z-index 0s 0.5s;
z-index:0;
}
.button .white {
background: #fff;
}
.button .black {
background: #000;
}
.button>div.hide {
transform:scale(0);
transition: transform .5s, z-index 0s 0.5s;
}
.button>div.u-on-top {
z-index:1;
}
<div class="button">
<div class="white u-on-top"></div>
<div class="black"></div>
</div>
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.