CSS double transition in-out renders strangely - css

I am trying to render beautiful pages transitions in my react application. I chose simple fade-in/fade-out transitions, and they work perfectly for random pages. But it happens that some pages are quite identical (just a text change), and in that case the transition is not so good (see code-pen bellow for a concrete example).
The way I understand mathematics, if I have two layers, let's say A and B, a linear fade-out of A starting above a similar linear fade-in of B should give constant pixels colors for pixels of the exact same color in both layers, isn't it? But as you can see in that codepen, it's not the case (i was hoping to have no visual effect as the two layers have the exact same color), and I don't understand why. Is there a way to obtain the desired result (for a given pixel, color B replace color A if they are different, but stay still if they are the same)?
https://codepen.io/st-phane-smirnow/pen/dyJaBPG
And here is the corresponding code - HTML:
<div class="a1">a1 div</div>
<div class="a2 hidden">a2 div</div>
<button>Switch divs<button>
CSS:
.a1, .a2 {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: red;
transition: all .5s linear;
}
.hidden { opacity: 0; }
button {
position: absolute;
top: 10px;
left: 10px;
}
JS:
window.addEventListener("load", () => {
document.querySelector("button").addEventListener("click", () => {
document
.querySelectorAll("div")
.forEach(d => d.classList.toggle("hidden"));
});
});
EDIT: I partially solved my problem by disabling the transparency of the underlying layer. That way, the application background, whatever it is, is hidden by the second layer (opacity always to 1), and the foremost layer just fade-in the underlaying layer. I don't know if, in a perfect world, that result is really different of what I would have got of a truly functional cross-fade, but at least it give a good result at screen.

It's because during the transition, they are both transparent so you're seeing the white background through them.
Here's an interesting post that shows some formulas that might help to why the resulting color isn't rgb(255,0,0) all the way through. Basically, some of the green and blue channel are being introduced.
It's reasonable to think that a 90% red + a 10% red would be a 100% red, but actually each layer is letting a little bit of 'white light' through. Here's how:
According to the post, the general formula for a 2 layer image is:
Y = p*T+(1-p)*B where...
p is the opacity 0...1 of the top layer,
T = rgb number of top layer color,
B is the rgb number of fully opaque bottom layer.
Y is the rgb number of the equivalent fully opaque color. 
So, say you have rgba(255, 0, 0, 0.9) on a white background. The resulting color is:
r = 0.9 * 255 + 0.1 * 255 = 255
g = 0.9 * 0 + 0.1 * 255 = 25.5
b = 0.9 * 0 + 0.1 * 255 = 25.5
Now let's add a rgba(255, 0, 0, 0.1) on top of that result:
r = 0.1 * 255 + 0.9 * 255 = 255
g = 0.1 * 0 + 0.9 * 25.5 = 22.9
b = 0.1 * 0 + 0.9 * 25.5 = 22.9
The extra green and blue channels are moving the color towards white.

Related

Translate CSS transition-timing-function to swiping

I am fading in a div (using CSS transitions) with a custom timing function (http://cubic-bezier.com/#1,0,1,1). The timing function is basically a more extreme version of 'ease-in'.
div {
opacity: 0;
transition: opacity 1s;
.in {
opacity: 1;
transition-timing-function: cubic-bezier(1, 0, 1, 1);
}
In addition to that, I want to be able to fade in the div by swiping across the screen. I am using the following Jquery to set the opacity according to how far the user has swiped:
documentWidth = $(document).width();
$(document).on('touchmove', function(e) {
// How much of the animation is completed (in %)
completion = e.changedTouches[0].pageX / documentWidth;
$('div').css('opacity', completion);
})
Nooooow, this is linear! Is there a clever Math-person out there who can figure out how to re-state that last line to represent my timing function?
So, for example, if completion is at 25%, the opacity should be around 2%. At 50%, it should be around 11% and at 75% it should be around 31%.
Start by finding a curve that approximates your cubic-bezier curve. With the given points and some online tools it's possible to draw a curve with this equation:
y = 464085.7 + (0.0174619 - 464085.7)/(1 + (x/22.88957)^4.174069)
in your case the x represents your completion variable and y the resulting opacity.
Then your code becomes
let completion = e.changedTouches[0].pageX / documentWidth;
let exponential = Math.pow((completion / 22.88957), 4.174069);
let opacity = 464085.7 + (0.0174619 - 464085.7)/(1 + exponential);
$('div').css('opacity', opacity);
(of course you may find a better equation that best fits your needs)

How to display an image progressively by chunks?

I'm trying to make a loading animation for a website : an image full of candles, each candle's appearing as the website loads. My mains objectives are :
Use minimal bandwidth
Maximize the picture quality
Create transition between each parts of the picture
So,
I tried to use the Photoshop legacy Export for Web features but the alpha layer (transparency) that I want to use in PNG is then tessellated, they are also subtle noise on some images.
I thought about using a video with embedded transitions but even a VP9 video is bigger than the PNG trick.
Using JPEG as well doesn't look great as I can't use transparency and the sum of all pictures are bigger than the PNG trick.
So that's why I wanted to know if someone had any idea how to do such thing ! I would be please to add more information about this if asked !
Edit
Here's the first picture of the sequence (without any edit to add transparency) :
And here's the last, there's 19 images total :
If you want to light each candle individually, I wouldn't know how to do that without using too many images, hence too much bandwidth. I can therefore not provide a solution that perfectly suits your requirements. I will, however, suggest an interim solution that might work until someone comes up with something better.
Idea:
Group the 30+ candles into three chunks, as they seem to be arranged in roughly three rows.
Although a bit tricky, the rock geometry does lend itself to be cut out accordingly as well.
Breakdown:
Create four pictures / layers:
All dark
One row lit
Two rows lit
All rows lit
Use JPG, as small as possible (maybe 1280px in width, ~70% quality)
Instead of using img, use one div with CSS background-image per layer
Place all layers on top of each other (layer 4 on top) with position: absolute
Use JavaScript to fade in another layer whenever a third of your page has loaded
The reason for using background-image is that you can easily use CSS to stretch the div containers to 100% width and height (assuming that the whole thing is supposed to be full screen) and make the images adapt to any resolution and aspect ratio easily. Upscaling a 1280px wide JPG to 1920px usually looks pretty okay, but you will have to play with image size and JPG quality to hit the sweet spot. With my suggested setup (see above), all four images should end up being about 400 to 500 KB combined.
Example:
Note that this is a rough mockup based on your first and last frame - you can surely do much better with the original material at hand.
var timer = new Array(3);
var fader = new Array(3);
var layer = new Array(3);
function fade() {
for (var i=0; i<3; ++i) {
layer[i] = document.getElementById("s" + (i+1));
layer[i].style.opacity = 0.0;
clearTimeout(timer[i]);
clearInterval(fader[i]);
start(i);
}
}
function start(i) {
timer[i] = setTimeout(function() {
fader[i] = setInterval(opacity, 20, i);
}, (2000*i));
}
function opacity(i) {
var style = window.getComputedStyle(layer[i], null);
var opacity = parseFloat(style.getPropertyValue("opacity"));
if (opacity >= 1) {
clearInterval(fader[i]);
} else {
layer[i].style.opacity = (opacity + 0.01);
}
}
.container {
position: relative;
width: 600px;
height: 417px;
}
.candles {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
opacity: 0.0;
background-size: 100%;
background-repeat: no-repeat;
background-position: center center;
}
#s0 {
background-image: url(http://i.imgur.com/ZQqeebI.jpg);
opacity: 1.0;
}
#s1 {
background-image: url(http://i.imgur.com/ah7UP3x.jpg);
}
#s2 {
background-image: url(http://i.imgur.com/zLgBA5x.jpg);
}
#s3 {
background-image: url(http://i.imgur.com/ar4w18n.jpg);
}
button {
width: 600px;
padding: 0.4em;
}
<div class="container">
<div id="s0" class="candles"></div>
<div id="s1" class="candles"></div>
<div id="s2" class="candles"></div>
<div id="s3" class="candles"></div>
</div>
<button onclick="fade()">Fade</button>
Here is an external link to the fiddle.

Unskewed child not aligning

Note: This question is about a problem with output, and not about creation of any shape.
I recently created a shape :
.prog {
position: relative;
top: 20px;
width: 150px;
height: 120px;
background: green;
display: inline-block;
transform: skewY(20deg);
transform-origin: 0% 100%;
transition: 0.8s ease;
}
.prog:before {
content: "";
position: absolute;
left: 150px;
width: 150px;
height: 120px;
background: red;
transform: skewY(-40deg);
transform-origin: 0% 100%;
transition: 0.8s ease;
}
<div class="prog "></div>
In the above snippet, the green shape is the .prog element, and is skewed. The red shape is :before pseudoelement of the first element.
I skewed .prog (skewY) to 20deg. Now, I needed :before to be -20deg. For this, I first had to unskew it. Then skew it further 20deg.
So final skewY value will be -40deg. I applied this, and appropriate transform-origins.
But the problem is that the top points of both the shapes aren't aligning. They should, but they aren't. Here's an image showing the problem :
The black lines are just for reference.
Now even more!
I skewed -20 -20 instead of -40 :
transform: skewY(-20deg) skewY(-20deg); <-- This works!
transform: skewY(-40deg); <---------------- This doesn't!
The behaviour of the "unskewed" child is normal, it is the way skew works. In order to understand this, I am going to simplify the question to :
why isn't skewX(40deg) the same as skewX(20deg) skewX(20deg)?
div {
width: 100px; height: 100px;
position:absolute;
top:20px; left:20px;
transform-origin: 0 0;
}
.d1 {
transform: skewX(40deg);
background: red;
opacity:0.7;
}
.d2 {
transform: skewX(20deg) skewX(20deg);
background: blue;
opacity:0.7;
}
/** FOR THE DEMO **/
body {background: url('http://i.stack.imgur.com/GySvQ.png');background-size: 10px;}
.m {text-align:right;padding-top:105px;}
.m1{width:83px;color:red;border-right:1px solid red;}
.m2 {width:72px;color:blue;border-right:1px solid blue;}
p{margin:0 0 5px 150px;color:red;}
.b{color:blue;}
<div class="d1"></div>
<div class="d2"></div>
<div class="m m1">x = 83</div>
<div class="m m2"><br/>x = 72</div>
<p class="r">skewX(40deg)</p>
<p class="b">skewX(20deg) skewX(20deg)</p>
Note: for the sake of explanation I will be using a 100*100 square div and the transform origin is set on the top left corner of this div. Like in the above code snippet.
To understand the difference between the two transformations, we need to explore the way the CSS skew() function works. The specs say :
A 2D skew transformation along the X axis with the parameter alpha is
equivalent to the matrix:
So this means we can calculate the coordinates of each point of a 2D X skewed element like this :
| 1 tan(α) | . | x |
| 0 1 | | y |
α is the X skewed angle
x/y the coordinates of the point before transformation
For skewX(40deg)
α = tan(40deg) ~= 0.83
| 1 0.83 | . | 0 | | 83 |
| 0 1 | | 100 | = | 100 |
The new coordinates are x = 83 as seen in the code snippet example.
For skewX(20deg) skewX(20deg)
α = tan(20deg) ~= 0.36
first skew :
| 1 0.36 | . | 0 | | 36 |
| 0 1 | | 100 | = | 100 |
Second skew :
| 1 0.36 | . | 36 | | 72 |
| 0 1 | | 100 | = | 100 |
The new coordinates are x = 72 as seen in the code snippet.
Conclusion
Both transformations don't give the same result. So skewY(20deg) skewY(-40deg) isn't the same transformation as skewY(-20deg) and the two top corners of the red and green elements can't align as :
tan(20deg) != tan(40deg)/2
References :
The CSS3 matrix() Transform for the Mathematically Challenged
CSS transform skew: math unveiled
CSS Transforms Module Level 1
skew introduces a vertical offset equal to the tangent of the angle. So, skew(20deg) introduces an offset of tan(20deg).
For your example to work, it should be that
tan(-20deg) = tan(20deg) + tan( -2 * 20deg)
or
tan (2 * x) = 2 * tan (x)
but this is not true, tangent and sum are not asociative
the required skew to reverse it is
result = - atan ( 2 * tan (x))
that, for x = 20, gives a result of
36,052388732387908475278040193987
(aproximately)
By way of a revision of my initial answer which produced an output rather than an explanation, I would postulate* that the effect is seen because you are surmising a negative skew can be used to offset a position on the positive skew curve, when in fact- at minus values you are operating on the negative skew curve.
This would first require that the measure of skew was singular and occurring on the same curve (see normal curve below), with positive and negative values allowing to shift along the curve.
However, the curve for negative and positive skews are inversly tailed.
Zero skew is the only value which operates the same on both. As such, if you have an element, apply a skew of 20 degrees to it, then apply a skew of minus 20 you will actually have a skew (positive or negative) of zero, so using a negative offset appears to work..
However, if you then apply additional negative skew, you will have a negatively skewed element, the curve for which is different and not equal to the inverse equivalent position on the positive skew curve.
20deg = Original element, 20deg on positive skew curve
20deg - 20deg = 0, same for positive and negative skew curve
-40deg = taking the elements current 20deg skew, minus 40 deg = 20deg on negative skew curve - NOT an equivalent 'opposite' point on the positive skew curve
When using psuedos, the skew works because you arent offsetting a positively skewed value by a newgatively skewed amount.
* Im no mathematician, so afraid I can only claim this as conjecture
I will just address to the main issue, as to why it happens, instead of trying to provide alternatives
Since your math appears to be exact, we have to search for what is not exact, which is the platform itself...
MDN's article on transform states that it is not a stable technology:
This is an experimental technology
Because this technology's specification has not stabilized, check the
(emphasis mine)
Consider the following:
When you skew the main div, it requires a huge amount of browser render calculations to show in a 2D environment what it would look like considering both 3 axis... The pseudo element suffer with those calculations, as you can see if you further it a little left from the edge where you've put it:
See that I just moved to the left, and that caused it to go down a little, according to the Y skewing applied to the main div. Now, add to that another huge amount of calculations when you re-skew the pseudo element... The browser will just fail to provide an accurate render of what the 3D space would look like in a 2D environment...

compute box-shadow width for an element

As you may know that box-shadow is not a part of box-model. so what could be a good way to compute the width of box-shadow that adds to an element?
Update: I need to know the total width of an element, including the shadow width.
well you could simply add a margin equal to the box-shadow. For example:
box-shadow: 0 0 10px #008800;
margin: 10px;
in the case you use the X and Y offsets on the box-shadow use add that value to the length of the shadow. Example:
box-shadow: 5px 5px 10px #080;
margin: 5px 15px 15px 5px;
here the offset is 5px, plus the 10px length. In the case of the spread we can continue to add to the margin values to take this into consideration.
box-shadow: 5px 5px 10px 7px #080;
margin: 12px 21px 21px 12px;
using the margin will keep the shadow from overlapping other objects on the page.
Exact width will differ from browser to browser. Each renders the shadows different. If i have to give a hard calculation for the object I guess it would be the something like this (the css property for reference)
box-shadow: h-shadow v-shadow blur spread color;
The box model offsets would be
top = (spread - v_shadow + 0.5*blur)
right = (spread + h_shadow + 0.5*blur)
bottom = (spread + v_shadow + 0.5*blur)
left = (spread - h_shadow + 0.5*blur)
The coefficient of the blur is a estimate, it may need to be adjusted slightly. Personally I prefer to not use the offset, but is here to show where it would be used
here is a jsfiddle to see it in action http://jsfiddle.net/YvqZV/4/
Just extending #samuel.molinski's answer by creating a complete function that takes a box shadow and returns the widths.
function getBoxShadowWidths(boxShadow) {
// not supporting multiple box shadow declarations for now
if ((boxShadow.match(/(rgb|#)/g) || []).length > 1) {
return false;
}
const regEx = /(\d(?=(px|\s)))/g;
const matches = [];
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset,
// blur, and spread. Below finds each one and pushes it into an array (regEx.exec when used in succession
// with a global regex will find each match.
let match = regEx.exec(boxShadow);
while (match != null) {
matches.push(match[0]);
match = regEx.exec(boxShadow);
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat);
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const top = spread - vOffset + 0.5 * blur;
const right = spread + hOffset + 0.5 * blur;
const bottom = spread + vOffset + 0.5 * blur;
const left = spread - hOffset + 0.5 * blur;
return { top, right, bottom, left };
}
Thanks #Joey for the function. I added support for multiple values:
function getBoxShadowWidths(boxShadowValues) {
const regEx = /(\d(?=(px|\s)))/g
const widths = { top: 0, right: 0, bottom: 0, left: 0 }
boxShadowValues.split(/\s*,\s*/).forEach(boxShadowValue => {
const matches = []
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset, blur, and spread.
// Below finds each one and pushes it into an array (regEx.exec when used in succession with a global regex will find each match.
let match = regEx.exec(boxShadowValue)
while (match != null) {
matches.push(match[0])
match = regEx.exec(boxShadowValue)
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat)
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const actualWidths = {
top: spread - vOffset + 0.5 * blur,
right: spread + hOffset + 0.5 * blur,
bottom: spread + vOffset + 0.5 * blur,
left: spread - hOffset + 0.5 * blur,
}
Object.keys(actualWidths).forEach(side => {
widths[side] = Math.max(widths[side], actualWidths[side])
})
})
return widths
}

Position a background image outside an element using percent [duplicate]

This question already has answers here:
Using percentage values with background-position on a linear-gradient
(2 answers)
Closed 3 years ago.
I don't know if it's the late hour or if I'm just being stupid, but I can't figure this out.
What I'm trying to do is position a background image just outside the element it belongs to using %. The reason I want to do is is so that I can later animate the background-position from this % to 50% having it "slide in".
If I could use pixels it's easy enough to set the background-position to [width-of-element]px 0 but as I want the element's final position to be 50% 0 I can't start with a pixel value. (I'm using the jQuery Background Position plugin btw http://keith-wood.name/backgroundPos.html).
So, my question is, knowing the width of the element and the width of the background image - how can I calculate which %:age is needed to position the image just outside the edge of the element?
Obviously setting the background position to "0 0" makes it render at the top left, "50% 0" makes it centered and "100% 0" positions it from the right edge. If I go above 100% it starts to move away from the right edge, and depending on the width of the image (and I guess, the element) any value from roughly 200 and up is needed to completely shove the background image outside the edge of the element.
If I go the other way around, from 0 and downward the image moves off to the right, again the % needed to hide it varies but is not the same as the positive % needed to push it off the other edge.
Again, maybe I'm just tired but I'm stuck here.
http://jsfiddle.net/cTeEA/
Edit: Another curious thing I noticed is that if the image is smaller than the containing element, increasing its background-position-x above 100% doesn't make it move away from the right instead it makes it move to the right. Adjust the 101% on this updated fiddle and compare with the old fiddle to see what I mean: http://jsfiddle.net/cTeEA/1/
Edit: Ok percentages seemed out of the question, or at least ten times harder than simply using pixels. Here's how I solved it (more or less):
var dir = 'left'; // || 'right' (where to slide in the image from)
var winWidth = $(document).width();
var imgWidth = $('img[src="src-of-already-added-and-loaded-img.png"]').width();
var posOutside = dir == 'right' ? '-' + imgWidth + 'px' : winWidth + 'px';
var posCenter = (imgWidth > winWidth) ? -((imgWidth - winWidth) / 2) : ((winWidth - imgWidth) / 2);
Then I just animated the background-position from posOutside to posCenter.
Thanks to those who helped in the comments as well.
Background position with percentage values is not easy.
You can see an explanation of the math involved here
In your case, the short answer is:
The background size is greater or smaller than the div by a factor of f. Then your percentage is 100 / (1 - f).
That means that:
The backgound size is the same than the div. You are out of luck, it's not posible.
The background is bigger. Say div=100 background=400, then f = 4 and the formula gives -33%. (first example in demo)
The background is narrower. Say div=400 background=100, f=0.25 and the formula gives 133% (second example in the demo)
demo
Notice that in the demo the percentages are a little bit offset to show that the background is really there
css:
div.foo {
width: 100px;
background-size: 400px 100px;
background: red url(http://placekitten.com/400/100) no-repeat 50% 0;
height: 100px;
background-position: -32% 0; /* Disappear to the left */
}
div.foo2 {
width: 400px;
background-size: 100px 100px;
background: red url(http://placekitten.com/100/100) no-repeat 50% 0;
height: 100px;
background-position: 132% 0; /* Disappear to the left */
}

Resources