I have a div that is positioned using top and left properties. And I need to dynamically switch the positioning from top/left to its transform equivalent. The problem is that when I apply top: 0, left: 0 and equivalent transform property, the div jumps to 0,0 for a split second, before positioning correctly. I understand that changing top and left properties triggers layout redraw.
Here is a example of this behaviour: codepen.
When applying Animation1 for the first time the described problem occurs, but it then works as expected after that.
<div class="red gpu" style="left: 120px; top: 100px;"></div>
<div class="border original">120,100</div>
<div class="border moved">270,180</div>
<button onclick="animate1()">Animate1</button>
<button onclick="animate2()">Animate2</button>
function animate1() {
let div = document.querySelector('div');
div.style.cssText = `
left: 0px;
top: 0px;
transform: translate(120px, 100px) translate(150px, 80px);`;
}
function animate2() {
let div = document.querySelector('div');
div.style.left = `0px`;
div.style.top = `0px`;
div.style.transform = `translate(120px, 100px)`;
}
div {
width: 100px;
height: 100px;
}
.red {
background-color: red;
transform-origin: 50% 50%;
transition: transform 2s;
position: absolute;
}
.border {
border: 1px solid blue;
position: fixed;
}
.original {
left: 120px;
top: 100px;
}
.moved {
left: 270px;
top: 180px;
}
Is there a way to prevent this from happening? I tried applying top/left and transform properties in separate requestAnimationFrames and using translate3d without any luck.
You need to set in the css what the starting state is and then change only the transform property.
function animate1() {
let div = document.querySelector('div');
div.style.transform = `translate(120px, 100px) translate(150px, 80px)`;
}
function animate2() {
let div = document.querySelector('div');
div.style.transform = `translate(120px, 100px)`;
}
div {
width: 100px;
height: 100px;
}
.red {
background-color: red;
transform-origin: 50% 50%;
transition: transform 2s;
position: absolute;
left: 0px; top: 0px;
transform: translate(120px, 100px);
}
.border {
border: 1px solid blue;
position: fixed;
}
.original {
left: 120px;
top: 100px;
}
.moved {
left: 270px;
top: 180px;
}
<div class="red gpu"></div>
<div class="border original">120,100</div>
<div class="border moved">270,180</div>
<button onclick="animate1()">Animate1</button>
<button onclick="animate2()">Animate2</button>
You are not animating top and left so it's changing them abruptly. You could try transition: all 2s; so that it applies to anything, or transition: top 2s, left 2s, transform 2s; to specify only the properties you are using.
NOTE: Animating non-transform properties can have a negative impact on browser performance so do that with caution, if you can set the original position with just the transform (with top/left 0) you would be better off and your original code would probably work as expected.
Related
I use position relative and absolute on the switch and handle respectively, then use left on the handle to move it to the end or start of 'cSwitch' but the transition duration i applied only works from left to right.
i tried applying right too but that didn't change anything.
const cSwitch = document.querySelectorAll('.cSwitch')[0];
cSwitch.onclick = () => {
(cSwitch.getAttribute('state') === 'on') ? cSwitch.setAttribute('state', 'off') : cSwitch.setAttribute('state', 'on');
}
.cSwitch{
position: relative;
width: 150px;
aspect-ratio: 2/1;
border-radius: 50px 50px;
background: crimson;
}
.cSwitch[state = on] .cSwitch_handle{
position: absolute;
left: 50%;
}
.cSwitch_handle{
width: 75px;
aspect-ratio: 1;
border-radius: 50%;
background: skyblue;
left: 0%;
transition: left 0.5s;
}
<div class="switch-wrapper">
<div class="cSwitch" state="off" id="neon-switch">
<div class="cSwitch_handle"></div>
</div>
</div>
You neglected to apply position: absolute in the default state, so left has no effect. left may transition from 50% back to 0%- but it doesn't apply, because the element is not positioned.
Put position: absolute into the rule with the .cSwitch_handle selector instead.
I've looked into this a fair bit but can't seem to find a good, solid answer to find how to make a responsive circle around a div element of variable height.
It's easy to make a simple responsive circle using vw units.
<div style="height:20vw; width:20vw"></div>
However, I'm looking to use a min-height of an element and have a circle around this div.
Another way to create a responsive circle is using something like the snippet below, but again I can't adapt this to work for a variable height (again, I can't use vh units as the div will change in height.
.square {
position: relative;
width: 10%;
background: gray;
border-radius: 50%;
}
.square:after {
content: "";
display: block;
padding-bottom: 100%;
}
.content {
position: absolute;
width: 100%;
height: 100%;
}
<div class="square">
<div class="content">
</div>
</div>
I am trying to create something like the below, where the circle will never cut into the corners of the div (with around a 10px padding). I personally was trying to avoid javascript and would have preferred a css only approach, but it seems it's unavoidable. Maybe the only solution is to use a jquery to calculate the height of the element in order to apply this to a wrapper element?
I was playing around with this:
.square {
position: absolute;
top: 50%;
display: inline-block;
left: 50%;
transform: translate(-50%, -50%);
min-height: 100px;
border-radius: 50%;
background: url('https://i.imgur.com/2dxaFs9_d.webp?maxwidth=640&shape=thumb&fidelity=medium');
background-size: 100% 100%;
padding: 20px;
}
.content {
width: 300px;
min-height: 100px;
background: tomato;
}
<div class="square">
<div class="content">
Hello!<br>
<br><br><br>This has a variable height but fixed width<br><br><br>Hello
</div>
</div>
Clip-path can easily do this if you consider solid coloration.
Resize the element and the circle will follow:
.box {
width: 200px;
height: 200px;
overflow: hidden;
resize: both;
background: blue;
box-shadow: 0 0 0 200vmax red;
clip-path: circle(71%);
margin: 100px auto;
}
<div class="box"></div>
Related question to understand the magic number 71%: clip-path:circle() radius doesn't seem to be calculated correctly
To use an image we can consider pseudo elements. You can also rely on calc() to add the offset:
.box {
width: 200px;=
resize: both;
clip-path: circle(calc(71% + 10px));
margin: 100px auto;
position: relative;
font-size:35px;
color:#fff;
}
/* the background layer */
.box::before {
content:"";
position: absolute;
z-index:-1;
top:0;
left:0;
right:0;
bottom:0;
background:blue;
}
/* the image layer */
.box::after {
content:"";
position: fixed; /* to make sure the image cover all the screen */
z-index:-2;
top:0;
bottom:0;
left:0;
right:0;
background:url(https://picsum.photos/id/1015/1000/1000) center/cover no-repeat;
}
<div class="box" contenteditable="true"> Edit this<br>text </div>
I tried my hardest to figure this out with pure css. Though the problem with css I could not figure out how to calculate the diameter of the circle based on the content div size; the length from top left corner to bottom right corner of the variable height div.
I'm not sure if can be done using the calc() css function.
But I did manage to do it with a little jquery (which could easily be changed to pure javascript if you are not using jquery).
See working resizable example below (follow my comments in code)
Note: If you are using internet explorer the resizable demo content div will not resize.
// circumscriber for variable size divs
function circumscriber() {
// for each variable size div on page
$(".variable-size").each(function() {
// get the variable size div content width and height
let width = $(this).outerWidth();
let height = $(this).outerHeight();
// get the diameter for our pefect circle based on content size
let diameter = Math.sqrt(width ** 2 + height ** 2);
// extra 15 pixel circle edge around variable size div
let edge = 15;
// add current circle size width css
$('.circle', this).css({
'width': (diameter + (edge * 2)) + 'px'
})
});
}
// run the circumscriber (you might wana do this on ready)
circumscriber();
// if the window is resized responsively
$(window).on('resize', function() {
circumscriber();
});
// for demo purpose to fire circumscriber when resizing content
// not needed for real thing
$('.content').on('input', function() {
this.style.height = "";
this.style.height = ( this.scrollHeight - 30 ) + "px";
circumscriber();
}).on('mouseup', function() {
circumscriber();
});
/* variable size container to be circumscribed by circle */
/* none of these styles are required, this just to center the variable size div in the window for demo purposes */
.variable-size {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* resizable text area for demo */
/* again not needed */
.variable-size .content {
padding: 15px;
background: #fff;
resize: both;
overflow: auto;
color: #000;
border: none;
width: 200px;
font-weight: bold;
}
.variable-size .content:focus {
outline: 0;
}
/* child circle div css */
.variable-size .circle {
position: absolute;
background-image: url('https://i.imgur.com/2dxaFs9_d.webp?maxwidth=640&shape=thumb&fidelity=medium');
background-position: center center;
z-index: -1;
border-radius: 50%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transition: all 0.5s ease;
width: 0;
}
/* fast way to make circle height the same as current width */
.variable-size .circle:before {
display: block;
content: '';
width: 100%;
padding-top: 100%;
}
/* demo window css */
HTML,
BODY {
height: 100%;
min-height: 100%;
background: black;
position: relative;
font-family: "Lucida Console", Courier, monospace;
}
<div class="variable-size">
<textarea class="content" rows="1" placeholder="TYPE TEXT OR RESIZE ME ↘"></textarea>
<div class="circle"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
See jsfiddle here... https://jsfiddle.net/joshmoto/6d0zs7uq/
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.stroke();
Source: https://www.w3schools.com/
You could use flex display and insert empty flex-items around the inner div and use flex-basis to fix their width.
Try this
.square {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
min-height: 100px;
border-radius: 50%;
background: black;
background-size: 100% 100%;
padding: 20px;
}
.content {
width: 300px;
min-height: 100px;
background: tomato;
}
.emptyDiv {
flex-basis: 120px
}
<div class="square">
<div class="emptyDiv"></div>
<div class="content">
Hello!<br>
<br><br><br>This has a variable height but fixed width<br><br><br>Hello
</div>
<div class="emptyDiv"></div>
</div>
Why are child positions affected when you transform the parent?
I want the blue box stay in the bottom right position of the yellow box. But when I translate the red box, the blue box will move to his (red) parent.
In real life box-red represents my ui-view in Angular. The views are sliding in and out. I can't change the HTML hierarchy.
See my codepen
https://codepen.io/benbesuijen/pen/GPOQjM
HTML
<div class="box-yellow">
<div class="box-red">
<div class="box-blue"></div>
</div>
</div>
CSS
.box-yellow {
background-color: yellow;
position: relative;
height: 200px;
width: 200px;
}
.box-red {
background-color: red;
height: 100px;
width: 100px;
}
.box-blue {
background-color: blue;
bottom: 0;
height: 50px;
position: absolute;
right: 0;
width: 50px;
}
.box-move {
transform: translateX(100%);
}
Take a look at the spec: The Transform Rendering Model
Specifying a value other than ‘none’ for the ‘transform’ property
establishes a new local coordinate system at the element that it is
applied to.
What that means here is that the blue element will become relative to the element with the transform (the red parent) - not relative to the viewport (like regular static elements)
However, we can solve this case by applying the transform to the yellow-box, and have the the blue one's position: fixed.
Below is an example:
var button = document.querySelector('button'),
boxRed = document.querySelector('.box-red');
button.addEventListener('click', function() {
boxRed.classList.toggle('box-move');
});
.box-yellow {
background-color: yellow;
transform: translate(0, 0);
float: left;
position: relative;
height: 200px;
width: 200px;
}
.box-red {
background-color: red;
height: 100px;
width: 100px;
}
.box-blue {
background-color: blue;
position: fixed;
bottom: 0;
right: 0;
height: 50px;
width: 50px;
}
.box-move {
margin-left: 50%;
}
button {
margin-left: 20px;
}
<div class="box-yellow">
<div class="box-red">
<div class="box-blue"></div>
</div>
</div>
<button>Translate red box</button>
Hope this helps :)
I think your only way is to use margin-left and calculate the size of the box. Something like:
button.addEventListener('click', function() {
var boxRedWidth = boxRed.getBoundingClientRect().width;
boxRed.style.marginLeft = boxRedWidth +"px";
});
It's due to the translateX essentially making it a relative position object, meaning the .box-blue jumps into that as its relative parent.
With margin-left the .box-red remains as static meaning it doesn't become a relative parent to box-blue.
I have a div .box with which has absolute position set at the bottom of the parent div. It does not have the top position set because the height differs for different .box divs. On hover over the parent div, I want to change its top position to 0 with the transition effect.
In the following code transition does not work if I do not define the top position by default. I have tried using top: auto and it still does not work. How can I use transition without defining the top position.
Here's my code:
HTML:
<div class="wrap">
<div class="box">
Box
</div>
</div>
CSS:
.wrap{
position: relative;
height: 200px;
width: 200px;
background: green;
}
.box{
background: yellow;
position: absolute;
bottom: 0;
left: 0;
transition: all 0.9s ease 0s;
top: 50%; /*== does not work without it ==*/
}
.wrap:hover .box{
top: 0;
bottom: 0;
}
Fiddle: http://jsfiddle.net/5hs09apn/
A slight different approach than using top: http://jsfiddle.net/5hs09apn/2/
set the height for the box, and on hover set it to 100%; let the bottom be zero, so you don't even need to use top
.box{
background: yellow;
position: absolute;
bottom: 0;
left: 0;
height:20px;
transition: all 1s ease 0s;
}
.wrap:hover .box{
height:100%;
bottom: 0;
}
.wrap {
position: relative;
height: 200px;
width: 200px;
background: green;
}
.box {
background: yellow;
position: absolute;
bottom: 0;
left: 0;
height: 20px;
transition: all 1s ease 0s;
}
.wrap:hover .box {
height: 100%;
bottom: 0;
}
<div class="wrap">
<div class="box">
Box
</div>
</div>
Add Top property in JQuery getting the current value of the Top of the .box using position().
This will be dynamic allocation of Top and will not effect your condition of varying .box height.
While doing this, you will have to add the hover effect in the JQuery part too, since the Top will be defined here and CSS wont know what to do with the hover.
Check the Fiddle here
This is the JQuery function added:
$(document).ready(function(){
var x = $('.box').position();
var myTop = x.top;
$('.box').css("top",myTop+"px");
$('.box').css("transition","all 0.9s ease 0s");
$('.wrap').bind('mouseover',function(){
$('.box').css("top","0px");
$('.box').css("bottom","0px");
});
$('.wrap').bind('mouseout',function(){
$('.box').css("top",myTop+"px");
$('.box').css("bottom","0px");
});
});
Hope this gives you what you need.
You can use:
.wrap:hover .box{
margin-bottom: 100%;
}
And try with different percentages until you get one you like. It's crude but I think it can work.
Is it possible to take a child outside of the flow of its transformed parent? Simple example below:
HTML:
<div class="parent">
<div class="child"></div>
</div>
Move parent
CSS:
.parent {
background: red;
height: 200px;
position: relative;
transition: 0.5s ease-in-out;
width: 200px;
}
.child {
background: black;
height: 100px;
left: 0;
top: 0;
position: absolute;
width: 100px;
}
.moved {
transform: translateX(100px);
}
Javascript:
$('#button').on('click', function(e) {
e.preventDefault();
console.log('test');
$('.parent').toggleClass('moved');
});
JSFiddle: http://jsfiddle.net/ous3s873/1/
Very simple stuff. Basically, the parent (red box) is being moved 100px to the right using a CSS transform. My desired output is that the child (black box) would stay in the same place as the parent moves behind it.
One option is adding a negative translateX to the child element in order to make it stay at its place:
Updated Demo
.moved .child {
transform: translateX(-100px);
}