CODE
I have 3 divs with text (1, 2, 3).
When the user hovers over each div, it should change the image.
I have a 1s ease transition to make it smooth.
BUG
If you hover over different divs too quickly, the transition is choppy or doesn't load. I'm looking for a solution where the user can quickly move through the divs and the image will transition slowly to whichever is the newest div. Thank you in advance.
CODEPEN DEMO
Codepen
LIVE DEMO:
img1 = 'url(https://images.unsplash.com/photo-1533273859801-d731381dfe2d)';
img2 = 'url(https://images.unsplash.com/photo-1534939444268-6a9ff2733c32)';
img3 = 'url(https://images.unsplash.com/photo-1534841515798-3d43f5434123)';
$('.p1').hover(function(){
$('.bg').css({'background-image': img1});
});
$('.p2').hover(function(){
$('.bg').css({'background-image': img2});
});
$('.p3').hover(function(){
$('.bg').css({'background-image': img3});
});
body {
margin: 0;
font-family: sans-serif;
}
.bg {
position: absolute;
width: 100%;
height: 100%;
background: url(https://images.unsplash.com/photo-1533273859801-d731381dfe2d) no-repeat center;
background-size: cover;
z-index: -1;
background-color: #989898;
background-blend-mode: multiply;
transition: background-image 1s ease
}
.projects {
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100vw;
height: 16em;
}
.p {
font-size: 1.2em;
position: relative;
padding: 2rem 0;
color: #fff;
opacity: 0.5;
letter-spacing: 4px;
text-indent: 4px;
font-weight: 400;
transition: opacity 0.5s ease;
text-align: center;
}
.p:hover {
opacity: 1;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="bg"></div>
<div class="projects">
<div class="p p1">1</div>
<div class="p p2">2</div>
<div class="p p3">3</div>
</div>
I suggest you to use the mousemove event and the .on() method instead of the .hover() method.
That, to be able to "buffer" events... Without missing one.
.hover() method is the same as defining a mouseenter and mouseout event handler (the second being optionnal).
With the mousemove event, the chances for the event to fire correctly (from a user point of view) are higher since it fires like a machinegun.
Why... Because you will have to "buffer" those events to wait for the current transition to end.
Now, you will attach the event handler to the common class .p and set a timeout of 600ms of "mouse inactivity". After that delay, the background wil be updated with the image corresponding with the last .p mousemoved.
While the user just move the mouse like someone with parkinson's desease, nothing happens about the background. It is updated and animated only on mousemove stop.
img1 = 'url(http://ejournalz.com/wp-content/uploads/2017/06/Dog-Care.jpg)';
img2 = 'url(https://www.focusdogtraining.com/wp-content/uploads/2017/10/puppies.jpg)';
img3 = 'url(https://www-tc.pbs.org/wgbh/nova/assets/img/full-size/clever-dog-lab-merl.jpg)';
var movement;
$(".p").on("mousemove",function(e){
var target = $(e.target);
clearTimeout(movement);
movement = setTimeout(function(){
console.log("User stopped moving... Updating the image.")
if(target.hasClass("p1")){
console.log("image #1");
$('.bg').css({'background-image': img1});
}
if(target.hasClass("p2")){
console.log("image #2");
$('.bg').css({'background-image': img2});
}
if(target.hasClass("p3")){
console.log("image #3");
$('.bg').css({'background-image': img3});
}
},600);
});
A working demo is best viewed on CodePen than in a SO snippet (sadly).
Ok. This is impossible using CSS, so here is a how to do it using jQuery.
To acheive the transition, we'll have to use two divs, one in the background and one in the front that will fade in giving us the illusion of transition. The .bg div should contain those two divs:
<div class="bg">
<div class="back"></div>
<div class="front"></div>
</div>
The css should change to:
.bg {
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
background-color: #989898;
background-blend-mode: multiply;
transition: background-image 1s ease
}
.bg > * {
background: url(http://ejournalz.com/wp-content/uploads/2017/06/Dog-Care.jpg) no-repeat center;
background-size: cover;
position: absolute;
width: 100%;
height: 100%;
}
Which just removes any css transitions and make the inner divs overlap and as big as their parent .bg.
Now for the js part - where the actual transition takes place. When the mouse enters a .p area, we just call the function changeTo with the corresponding image. The function takes the current image from the front to the back, sets the new image as the front image. Doing that will be choppier than the css transition because the opacity doesn't match. Making the front image the back one should be accompaigned with an invertion of the opacity of the front. That is achieved by just getting the opacity and substracting it from 1. After we do that, we just animate the front image opacity from whatever value it is now to 1. The speed of this animation should be relative to what the opacity is:
var $back = $(".bg .back"),
$front = $(".bg .front");
function changeTo(img) {
$front.stop(); // stop any animation that may be in progress
var op = $front.css("opacity"); // get the opacity value (stoping the animation will let the opacity hanging between 0 and 1 inclusive, so we get that value to transition from it)
$back.css("background-image", $front.css("background-image")); // make the front image the back one
$front.css({
opacity: 1 - op, // because we made the front image the back one we need to invert the front image's opacity to give the illusion that the back image (which is now the front one) is shown at the original opacity. That's how math works!
"background-image": img // and of course make the front image our desired one
});
$front.animate({ // animate the front image's
opacity: 1 // ... opacity from whatever value it is to 1
}, 500 * op, "linear"); // in a period of 0.5s accouunting for the opacity delta, in other words animating from opacity 0.5 to 1 will take only half the time as animating from 0 to 1. That also how math works! :P
}
$('.p1').on("mouseenter", function() { // when mouse enters .p1
changeTo(img1); // change to img1
});
$('.p2').o...
Working example:
var img1 = 'url(http://ejournalz.com/wp-content/uploads/2017/06/Dog-Care.jpg)';
var img2 = 'url(https://www.focusdogtraining.com/wp-content/uploads/2017/10/puppies.jpg)';
var img3 = 'url(https://www-tc.pbs.org/wgbh/nova/assets/img/full-size/clever-dog-lab-merl.jpg)';
var $back = $(".bg .back"),
$front = $(".bg .front");
function changeTo(img) {
$front.stop();
var op = $front.css("opacity");
$back.css("background-image", $front.css("background-image"));
$front.css({
opacity: 1 - op,
"background-image": img
});
$front.animate({
opacity: 1
}, 500, "linear");
}
$('.p1').on("mouseenter", function() {
changeTo(img1);
});
$('.p2').on("mouseenter", function() {
changeTo(img2);
});
$('.p3').on("mouseenter", function() {
changeTo(img3);
});
.bg {
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
background-color: #989898;
background-blend-mode: multiply;
transition: background-image 1s ease
}
.bg > * {
background: url(http://ejournalz.com/wp-content/uploads/2017/06/Dog-Care.jpg) no-repeat center;
background-size: cover;
position: absolute;
width: 100%;
height: 100%;
}
.projects {
position: absolute;
margin: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100vw;
height: 16em;
}
.p {
font-size: 1.2em;
position: relative;
padding: 2rem 0;
color: #fff;
opacity: 0.5;
letter-spacing: 4px;
text-indent: 4px;
font-weight: 400;
transition: opacity 0.5s ease;
text-align: center;
}
.p:hover {
opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="bg">
<div class="back"></div>
<div class="front"></div>
</div>
<div class="projects">
<div class="p p1">1</div>
<div class="p p2">2</div>
<div class="p p3">3</div>
</div>
Related
I have a hover effect where when the icon image is hovered over, an larger image appears (for clarity). I want this larger image effect to end after three seconds AND still have the hover ability. If the image is moved off of, then came back to; I want the larger image to load for another three seconds. Every time the image is hovered over, the effect would last three seconds.
I have tried CSS animations, transitions, setTimeout and none of them are working like I need. Any help is appreciated. I have a LOT of code on this project, so I will try to only include the relevant parts. Thanks in advance.
I will have the code added to the question, once i figure out what I am doing wrong.
Code for building levels for hover image
#PlayerMarker1 {
position: absolute;
left:2%;
top: 2%;
z-index: 9;
}
#Player1Final{
position: absolute;
left:2%;
top: 2%;
z-index: 9;
}
/* Elements for Image load on hover*/
.playerMarker img{
margin: 0 5px 5px 0;
}
.playerMarker:hover{
background-color: transparent;
}
.playerMarker:hover img{
border: 1px;
}
.playerMarker span{ /*CSS for enlarged image*/
position: absolute;
padding: 0px;
left: -1000px;
/*background-color: black ;*/
visibility: hidden;
color: black;
text-decoration: none;
}
.playerMarker span img{ /*CSS for enlarged image*/
border-width: 0;
padding: 2px;
}
.playerMarker:hover span{ /*CSS for enlarged image*/
visibility: visible;
top: 0;
left: 100px; /*position where enlarged image should offset horizontally */
z-index: 50;
}
Code for defining the images.
<div id="Player1Test">
<a id="PlayerMarker1" href="#thumb1"><img src="Player Markers/Morty_Icon.png" width="auto" height="auto"/><span><img src="Player Images/Morty_Token.png" /><br /></span></a>
</div>
This script adds the playerMarker classes to the element I need.
/* Script to add class to player marker ID items */
function Player1Function() {
var Player1FinalTest = document.getElementById("PlayerMarker1");
Player1FinalTest.classList.add("playerMarker");
Player1FinalTest.id='Player1Final';
}
Seems like css animations to pulse the image would work fine. Run the code snippet to try.
img {
margin: 25px;
width: 100px;
height: auto;
}
img:hover {
animation: pulse 2s 1;
}
#keyframes pulse {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
}
50% {
transform: scale(1.4);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
<h4>Hover the mouse over image<h4>
<img src="https://stackoverflow.design/assets/img/favicons/apple-touch-icon.png">
Yogi has a good answer using an animation that could be adapted to clearly move your element. I wanted to add an answer manipulating the left and top values using delay.
You are essentially moving a hidden image from off screen onto the screen. Though this feels a bit strange to do, as there may be more clear ways of accomplishing this task, you can immediately move the left into view, and delay moving the top out of view.
A different delay is needed for the hover and for the base element, so it returns to the original position immediately and is available for reuse, but delays moving away on hover.
This might keep in spirit of your current project.
If you have any questions, please ask 🚀
.playerMarker {
background-color: lightblue;
--size: 4rem;
height: var(--size);
width: var(--size);
}
.playerMarker span {
position: absolute;
padding: 0px;
top: 0;
left: -1000px;
visibility: hidden;
color: black;
text-decoration: none;
transition: top 0s;
}
.playerMarker span img {
border-width: 0;
padding: 2px;
}
.playerMarker:hover span {
transition: top 0s 2s;
visibility: visible;
top: -1000px;
left: 100px;
z-index: 50;
}
<div class="playerMarker">
<span>
<img src="https://stackoverflow.design/assets/img/favicons/apple-touch-icon.png" />
</span>
</div>
I have this problem where I am trying to move a CSS image in correspondence to time but can not use it because it is in CSS. The feature is not the sun, but that yellowish half-circle animation that can be seen in this pen. I am trying to apply that half circle to a completely random shape.
For instance, if the shape is completely random and is on a white canvas and there is a random deformed circle in the middle, then how to fill that circle with the same animation as seen in this pen and how to convert that CSS to javascript or how to control the CSS, because it has to stop and move when certain values are set in.
I do not expect someone to do the whole thing, but rather maybe assist on where I should start when I need to use that yellowish feature as seen in that pen.
Thank you.
Here is the thing.
<div class="sunmoon">
<h2>Sun & Moon</h2>
<div class="sun-times">
<div class="sun-path">
<div class="sun-animation"></div>
</div>
<div class="sun-symbol-path"><span class="symbol">☀</span></div>
</div>
<div class="legend">
<div class="sunrise">05:30 AM</div>
<div class="sunset">8:04 PM</div>
</div>
<div class="clear"> </div>
</div>
<div class="controls">
<button class="start">Start</button>
<button class="reset">Reset</button>
</div>
#import "compass/css3";
$arc-diameter: 170px;
.sunmoon {
position: relative;
& > div {
margin-left: 10px
}
}
.sun-times {
margin-top: 40px;
width: 230px;
height: 60px;
border-bottom: 1px solid #999;
overflow-y: hidden;
.sun-path {
margin-left: 25px;
width: $arc-diameter;
height: $arc-diameter;
overflow: hidden;
border: 1px dashed #999;
border-radius: 50%;
}
.sun-symbol-path {
position: absolute;
color: yellow;
text-shadow: 0 0 5px black;
height: $arc-diameter / 2;
-webkit-transition: -webkit-transform 2s linear;
-webkit-transform-origin: 50% 100%;
-webkit-transform: rotateZ(-75deg);
left: ($arc-diameter / 2) + 25px;
bottom: 0;
.symbol {
position: relative;
font-size: 16px;
top: -8px;
}
}
.sun-animation {
width: 0px;
height: 150px;
background-color: rgba(255, 255, 0, 0.4);
-webkit-transition: width 2s linear;
transition: width 2s linear;
}
}
.legend {
position: absolute;
bottom: 1em;
& > div {
position: absolute;
font-size: 12px;
width: 80px;
}
.sunrise {
left: 15px;
}
.sunset {
left: 185px;
}
}
body {
background-image: url(foo);
background-color: #ccc;
font-family: Helvetica, Sans serif;
h2 {
font-size: 20px;
}
}
.controls {
margin-top: 50px;
}
$('.start').click(function () {
$('.sunmoon .sun-animation').css('width', '70%');
$('.sun-symbol-path').css('-webkit-transform', 'rotateZ(27deg)');
// TODO: mention that this isn't nice
// city.find('.sunmoon .sun-animation').css('-webkit-transform', 'scaleX(50)');
return false;
});
$('.reset').click(function () {
$('.sun-animation').css('width', '0%');
$('.sun-symbol-path').css('-webkit-transform', 'rotateZ(-75deg)');
return false;
});
You can achieve it creating a shape in Illustrator with the inside transparent and the outside in the color that you want, and setting another box (with a new color, in this case yellow, and the same width of the shape), underneath that shape (e.g. using z-index) with position:absolute and left:-100%, and onClick, start and stop the transition to right.
I'll recommend you to use GSAP TimeLineMax. It lets you play and stop the transition with its functions, e.g.:
//off course after document load.
let animation = new TimelineMax();
animation
.to(
".underneath-box", //box class
10, //seconds
{
left:"100%", //100% of the width
ease: Power4.easeInOut //ease effect.
});
animation.pause(); //To prevent start.
$('start-button').click(function(){ //on start button click
animation.play().timeScale(1); //start animation
});
$('reset-button').click(function(){ //on reset button click
animation.reverse().timeScale(2); //reverse the entire animation
});
I'm assuming that you know some Html, and css basics. Don't forget to create those divs and buttons with its classes. Cheers.
Well, I had some fun figuring this one out. Not sure that's what you wanted but it's what I've got. Plain JS.
var c1 = document.getElementById("canvas1");
var c2 = document.getElementById("canvas2");
var ctx = c1.getContext("2d");
ctx.beginPath();
ctx.arc(100, 100, 90, 0, 2 * Math.PI);
ctx.lineWidth = 2;
ctx.stroke();
animateCanvas();
function animateCanvas(){
var w = 0;
var timer = setInterval(function(){
c2.width = w;
w += 1;
var ctx = c2.getContext("2d");
ctx.beginPath();
ctx.arc(100, 100, 89, 0, 2 * Math.PI);
ctx.fillStyle = "#efba32";
ctx.fill();
if (w===200){clearInterval(timer)}
}, 20);
}
.canvases{
position: absolute;
top: 0;
left: 0;
}
<canvas id="canvas1" class="canvases" width="200" height="200"></canvas>
<canvas id="canvas2" class="canvases" width="200" height="200"></canvas>
The example you've attached uses two divs, and outer and an inner, to create this effect. The outer div has a border radius property to make it look like a half circle. The inner div is a normal rectangle with overflow set to hidden. So the script creates the optical illusion of wiping to the right by animating the width of the inner div going from 0% of the outer div to 70%. To make this illusion work with a polygon, you would need to use something like clip-path instead of border-radius.
Here is an example of an arbitrary polygon, that will wipe right with a different background color. HTML:
<div>
<div class="outer"><div class="inner"></div></div>
</div>
CSS:
.outer {
margin-left:200px;
background-color: lightgray;
width: 300px;
height: 100px;
display: block;
overflow: hidden;
-moz-clip-path: polygon(0px 0px, 300px 0px, 300px 300px);
-webkit-clip-path: polygon(0px 0px, 300px 0px, 300px 300px);
clip-path: polygon(0px 0px, 300px 0px, 300px 300px);
}
.inner {
background-color: red;
width :0;
height: 300px;
display: block;
}
jQuery:
$('.outer').click(function() {
$('.inner').animate({width:"150px"}, 1800)
});
Here is a working fiddle: https://jsfiddle.net/r93pocgt/
My implementation uses jQuery's animate to change the CSS property for width, when you click on the outer div. I've done my best to simplify it to make it clear what's doing what.
I'm trying to switch images in a div every few seconds
the current code works, but there are 2 things that I want to change and I need your help for that:
The div resizes to the current image displaying in it, I want it to always use the size of the bigger image
I want to fade between the images instead of just switching
Thanks for reading, I hope you can help me.
This is what I got so far:
var imgIndex = 0;
setInterval(function() {
images[imgIndex].style.display = "none";
imgIndex++;
if (imgIndex >= images.length) {
imgIndex = 0;
}
images[imgIndex].style.display = "block";
}, 5000);
.imageDisplay {
display: inline-block;
width: 100%;
background-Color: white;
color: black;
border-radius: 5%;
margin: 2px;
padding: 1px;
opacity: 0.5;
transition-duration: 0.5s;
}
.imageDisplay:hover {
opacity: 1;
}
.Image {
width: 99%;
height: auto;
margin: 1px;
padding: 1px;
border-radius: 5%;
cursor: pointer;
display: none;
}
<div class="imageDisplay">
<p>Description</p>
<img class="Image"></img>
<img class="Image"></img>
</div>
Firstly you could set the div to be equal to the size of the large image and then use:
object-fit: cover;
object-position:center;
As for switching you could use:
transition: opacity .3s linear;
Read here on how to exactly implement the transitions. My suggestion would be stacking them all up and fading them in one by one every few seconds.
https://www.w3schools.com/css/css3_transitions.asp
http://css3.bradshawenterprises.com/cfimg/
i am looking for this kind of template . Moving the page to left and then page to right. Can anyone tell me how can i make this or is there any javascript example similar to this.
Create two <div>s, put them next to each other, make them take up the whole window, and change them as needed.
HTML:
<div class="left">left</div>
<div class="right">right</div>
CSS:
body {
margin: 0;
}
.left {
background-color: green;
bottom: 0;
left: 0;
position: fixed;
top: 0;
transition: width 1s;
width: 0;
}
.left.active {
width: 200px;
}
.right {
background-color: red;
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
transition: left 1s;
}
.right.active {
left: 200px;
}
JS (width jQuery):
$('.right').on('click', function() {
$('.left').toggleClass('active');
$('.right').toggleClass('active');
});
And here's a fiddle.
Using .toggle(effect,options,duration) method to moving the page to left to right.
// Set the effect type
var effect = 'slide';
// Set the options for the effect type chosen
var options = { direction: 'right' };
// Set the duration (default: 400 milliseconds)
var duration = 700;
$('#Id').toggle(effect, options, duration);
Taken via this link
If you want it to animate smooth on all devices you should use css transitions and transforms. Hiding and showing would be as basic as toggling a class then.
The example in jsfiddle
<style media="screen">
.wrapper {
width: 100%;
overflow: hidden;
}
.menu {
height: 100vh;
width: 100px;
background: #ABC;
color: white;
position: absolute;
left:0;
transition: transform 0.3s;
transform: translateX(-100px);
}
.content {
transition: transform 0.3s;
}
.active .menu {
transform: translateX(0);
}
.active .content {
transform: translateX(100px);
}
</style>
<button class="toggle">Toggle</button>
<div class="wrapper">
<div class="menu">
My menu
</div>
<div class="content">
My content
</div>
</div>
<script type="text/javascript">
document.querySelector('.toggle').addEventListener('click', function(event) {
event.preventDefault();
document.querySelector('.wrapper').classList.toggle("active");
});
</script>
NB! Supported from IE10. IE 9 will support without the animation and you probably should add the needed -ms-, -webkit-, -moz-, etc prefixes to support the older browsers if needed for transition and transform properties.
Also I advise not animating body or html with this method and put the content of page in the wrapper (in .content in the examples case). Moving body and html directly may lead to unpleasant surprises later.
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.