calculate radius of a circle using javascript - javascript

I am trying to calculate radius of a circle using javascript. I have following section with css
.circle {
position: absolute;
width: 100px;
height: 100px;
border-radius: 70px;
background: red;
}
<section class="circle"></section>
As the width and height of this circle is 100x100. How can I calculate its radius?

Since the radius is just half of the diameter, this is easy. The diameter is 100px, per width and height. Hence, radius is 100px / 2 = 50px.

While you could set the radius relatively by border-radius: 50%, you could simply divide the width/height of the box by 2 to get the radius.
For instance:
var circle = document.querySelector('.circle'),
radius = circle.offsetWidth / 2;
circle.innerHTML = "Radius: " + radius + "px";
.circle {
position: absolute;
width: 100px;
height: 100px;
border-radius: 50%; /* I don't know if you really need to get the value of this */
background: red;
line-height: 100px;
text-align: center;
}
<section class="circle"></section>

If you just need to set a radius to make a perfect circle, use 50% radius. This way it doesn't depend on width/height and you don't need javascript:
.circle {
position: absolute;
width: 100px;
height: 100px;
border-radius: 50%;
background: red;
}
<section class="circle"></section>

Related

Absolute positioning messed up by CSS rotation

I've made a tiny example below showcasing the behavior I currently get and the behavior I want.
// Rotated div
rotated.style.left = "50px";
rotated.style.top = "100px";
// Original "untouched" div
original.style.left = "50px";
original.style.top = "100px";
// Where the rotated div *should* be
expected.style.left = "-10px";
expected.style.top = "160px";
div {
position: absolute;
height: 80px;
width: 200px;
opacity: 0.5;
mix-blend-mode: overlay;
}
#rotated {
transform: rotateZ(90deg);
background: blue;
}
#original {
background: red;
}
#expected {
transform: rotateZ(90deg);
background: green;
}
<div id="rotated"></div>
<div id="original"></div>
<div id="expected"></div>
The red div is the "original" div that I have not applied any transformations to. The blue div is rotated by 90 degrees. The red and blue div are both shifted by the same values, but clearly their corners don't line up. The green div is the expected (desired) position of the blue div.
As you can see, the left and top is not really working as desired. I understand why it isn't, but I'm looking for some solutions or workarounds. I have searched online and found the transform-origin property but I've got some problems using it. This is because the elements I'm looking to move are created dynamically. They have unknown widths and heights, and on top of that, the widths and heights will change later on!
I know for this static example I can just add transform-origin: 40px 40px; to (which is just the height / 2 twice) div#rotated and it'll work, but in my project that means I'd have to set this property on every element and update it every time I update the element's dimensions.
I just don't think this is that great and I'm looking for one of two possible solutions:
A pure CSS solution that somehow gets the height of the selected element and uses that as the transform-origin (or just any pure CSS solution that works)
Using JavaScript to calculate the corrected position (in this static example, I should be able to get -10, 160 as the position of the element) every time I want to move an element.
--- update ---
This problem is further complicated because if the rotation is 180deg or 270deg then the transform-origin of 40px 40px no longer works. I'd have to compute a new transform-origin every time I want to move an element... This is something I'd really like to avoid...
You could add a translate in your expected transform
.wrapper {
position: relative;
margin: 200px 0 0 200px;
width: 200px;
height: 200px;
border: 1px solid black;
}
div {
position: absolute;
height: 80px;
width: 200px;
opacity: 0.5;
mix-blend-mode: overlay;
}
#rotated {
transform: rotateZ(90deg);
background: blue;
}
#original {
background: red;
}
#expected {
transform: rotateZ(90deg) translateY(-100%);
background: green;
transform-origin: 0 0;
}
<div class="wrapper">
<div id="rotated"></div>
<div id="original"></div>
<div id="expected"></div>
</div>
I put all in a wrapper with a border. Transform origin is from bounding box
second snippet with several transform rotated + translate
if I still didn't understand exactly the exact rotation you need, you can play with parameters translate:
translateY
translateX
translate with 2 values
always check the wrapper around it's the bounding box: 0 0 refers to top left
.wrapper1 {
position: relative;
top: 100px;
left: 100px;
width: 200px;
height: 200px;
border: 1px solid black;
}
.wrapper2 {
position: relative;
top: 250px;
left: 100px;
width: 200px;
height: 200px;
border: 1px solid black;
}
.wrapper3 {
position: relative;
top: 400px;
left: 100px;
width: 200px;
height: 200px;
border: 1px solid black;
}
.wrapper4 {
position: relative;
top: 550px;
left: 100px;
width: 200px;
height: 200px;
border: 1px solid black;
}
div {
position: absolute;
height: 80px;
width: 200px;
opacity: 0.5;
mix-blend-mode: overlay;
}
.rotated1 {
transform: rotateZ(90deg);
background: blue;
}
.rotated2 {
transform: rotateZ(90deg) translateY(-100%);
transform-origin: 0 0;
background: blue;
}
.rotated3 {
transform: rotateZ(90deg) translateY(-50%);
transform-origin: 0 0;
background: blue;
}
.rotated4 {
transform: rotateZ(90deg) translate(-50%, -50%);
transform-origin: 0 0;
background: blue;
}
.original {
background: red;
}
<div class="wrapper1">
<div class="rotated1"></div>
<div class="original"></div>
</div>
<div class="wrapper2">
<div class="rotated2"></div>
<div class="original"></div>
</div>
<div class="wrapper3">
<div class="rotated3"></div>
<div class="original"></div>
</div>
<div class="wrapper4">
<div class="rotated4"></div>
<div class="original"></div>
</div>
For now, I've solved this by computing the correct transform-origin from the angle of rotation (which is always 0, 90, 180, or 270). The code is in TypeScript:
export function computeTransformOrigin(element: HTMLElement) {
const { width, height, transform } = getComputedStyle(element);
if (transform && transform !== "none") {
const values = transform.match(/^matrix\((.+)\)$/)?.[1].split(", ");
if (values) {
element.style.translate = "";
const [a, b] = values.map(Number);
const angle = (Math.round(Math.atan2(b, a) * (180 / Math.PI)) + 360) % 360;
if (angle === 0 || angle === 90) return parseFloat(height) / 2 + "px " + parseFloat(height) / 2 + "px";
if (angle === 180) return "center";
element.style.translate = "0 " + (parseFloat(width) - parseFloat(height)) + "px";
return parseFloat(height) / 2 + "px " + parseFloat(height) / 2 + "px";
}
}
return "center";
}
For no rotation or 90 degrees, we can get the transform origin as the height of the element divided by 2 (40px 40px). With 180 degree rotation, we use center, and if it's 270, we have to do some extra magic. The transform origin is the same as 90 degrees but we also have to translate the element down by the width minus the height.
Then when I update the angle for an element, I only need to update the transform origin at the end:
set angle(v: number) {
this.#angle = v % 360;
this.element.style.transform = `rotateZ(${v}deg)`;
if (v === 180) {
this.name.style.transform = `rotateZ(${v}deg)`;
} else {
this.name.style.transform = "";
}
this.element.style.transformOrigin = computeTransformOrigin(this.element);
}

How to get the circumscribed area of all DOM elements?

I have a lot of elements on the page. I need to draw a circumscribed rectangle that contains all DOM elements.
For that I iterate DOM elements and get rectangle:
Array.from(firstChild.children).forEach((child: Element) => {
const rect = child.getBoundingClientRect();
});
Which props I need from rect to do that?
Logically I need to get minimal x,y of left-top corner and max bottom-right corner. But how?
To apply a border you need to set 4 values: top, left, width and height.
Getting top and left can be easily done by finding the topmost and leftmost elements. To get the other values you should do some calculation:
width = rightmost - leftmost and height = bottommost - topmost
To get rightmost and bottommost you have to loop through all elements and get their rightmost/bottommost points with the same formula, but you need to rearrange them. Here you should keep the biggest values.
//select all elements that should be inside the rectangle
const myElements = document.querySelectorAll("div#container *")
//set initial values, I made sure they are always smaller/bigger than they will be
let Top=Infinity, Left=Infinity, Bottom=-Infinity, Right=-Infinity;
for(const i of myElements){
//loop through the elements
const data = i.getBoundingClientRect()
Top = Math.min(Top, data.top)
Bottom = Math.max(Bottom, data.top+data.height)
Left = Math.min(Left, data.left)
Right = Math.max(Right, data.left+data.width)
}
console.log(Top, Left, Bottom, Right) // print out the coordinates
//set the border
//I subtract 1px bacuse of the border width
const myBorder = document.querySelector("#border")
myBorder.style.top=Top-1+"px"
myBorder.style.left=Left-1+"px"
myBorder.style.width=Right-Left+"px"
myBorder.style.height=Bottom-Top+"px"
#t1{
position: absolute;
top: 20px;
left: 40px;
width: 60px;
height: 40px;
background-color: #000;
}
#t2{
position: absolute;
top: 30px;
left: 80px;
width: 40px;
height: 70px;
background-color: #111;
}
#t3{
position: absolute;
top: 50px;
left: -40px;
width: 60px;
height: 40px;
background-color: #800;
}
#t4{
position: absolute;
top: 35px;
left: 110px;
width: 30px;
height: 40px;
background-color: #444;
}
#border{
border: solid red 1px;
position: absolute;
}
<div id="container">
<div id="t1"></div>
<div id="t2">
<div id="t3"></div>
</div>
<div id="t4"></div>
</div>
<div id="border"></div>

showing a map marker at the exact location in css

So I have a database with x and y quadrants and I have a 350x350px map. I have positioned the map as such:
background-image: url(/storage/maps/surface.png);
background-repeat: no-repeat;
height: 350px;
background-position: center center;
margin: 0px auto;
background-size: cover;
The width seems to be 429px, not sure why. Im sure it has to do with the cover.
On top of this image I have a marker:
font-size: 32px;
color: #f9e4b4;
z-index: 5;
position: absolute;
top: 78px;
left: 349px;
The top represents the Y position and the X position represents the left.
These values (top and left) come from the database and are set in React JS.
This, as it stands creates three divs:
<div class="location-map mb-3">
<div style="background-image: url("/storage/maps/surface.png"); background-repeat: no-repeat; height: 350px; background-position: center center; margin: 0px auto; background-size: cover;">
<i class="fas fa-map-marker-alt player-icon" style="top: 78px; left: 349px;"></i>
</div>
</div>
The issue I am having is:
As you an see, I am trying to position this marker at a pixel perfect position on the map.
now as you see the location states 349, 78 and while this might be right css wise, the marker should be at the edge of the map (on the right) if it was truly 349px's to the left.
So my question is, is the image too small? did I position the image properly? Why is the marker where it is and not where I want it to be?
There are two main items to solve:
The size of the map element should be an exact 350px square
Using top and left should place the base of the pin at the exact coordinates.
Let's start with the map. If you know the exact image dimensions of your image this should suffice:
.map {
background: url(http://placehold.it/350x350);
height: 350px;
width: 350px;
}
<div class="map">
</div>
Now lets create the element we want to position using top and left to the exact pixel, allowing any decoration to fall where it may by using a pseudo element:
.map {
background: url(http://placehold.it/350x350);
height: 350px;
position: relative;
width: 350px;
}
.map-x-pin {
background-color: black; /* So we can see where the pixel is */
height: 1px;
position: absolute;
width: 1px;
}
.map-x-pin::after {
background-color: red; /* Decorative, this could be an image too */
bottom: 100%;
content: '';
display: block;
left: -10px; /* Half of the width, give or take a pixel */
position: absolute;
height: 40px;
width: 20px;
}
<div class="map">
<div class="map-x-pin" style="top: 78px; left: 349px;"></div>
</div>

Calculate div width/height when inside a zoom/scale transform

I have a div inside another div with transform scale applied.
I need to get the width of this div after the scale has been applied. The result of .width() is the original width of the element.
Please see this codepen:
https://codepen.io/anon/pen/ZMpBMP
Image of problem:
Hope this is clear enough, thank you. Code below:
HTML
<div class="outer">
<div class="inner">
</div>
</div>
CSS
.outer {
height: 250px;
width: 250px;
background-color: red;
position: relative;
overflow: hidden;
}
.inner {
background-color: green;
height: 10px;
width: 10px;
transform: translate(-50%);
position: absolute;
top: 50%;
left: 50%;
transform: scale(13.0);
}
JS
$(function() {
var width = $('.inner').width();
// I expect 130px but it returns 10px
//
// I.e. It ignores the zoom/scale
// when considering the width
console.log( width );
});
Use getBoundingClientRect()
$(function() {
var width = $('.inner')[0].getBoundingClientRect();
// I expect 130px but it returns 10px
//
// I.e. It ignores the zoom/scale
// when considering the width
console.log(width.width);
});
https://jsfiddle.net/3aezfvup/3/
i achieved your 130 by this
var x = document. getElementsByClassName('inner');
var v = x.getBoundingClientRect();
width = v.width;
You need the calculated value. This can be done in CSS.
Use calc() to calculate the width of a <div> element which could be any elements:
#div1 {
position: absolute;
left: 50px;
width: calc(100% - 100px);
border: 1px solid black;
background-color: yellow;
padding: 5px;
text-align: center;
}
I found this about this topic.
Can i use Calc inside Transform:Scale function in CSS?
For JS:
How do I retrieve an HTML element's actual width and height?

max scale in middle instead of end

In the following snippet, I am trying to achieve an effect where the div which appears in the middle of the visible scroll section is at full scale scale(1); and the other div's scale falloff towards scale(0); as they approach the edges.
I have drawn a debug box in the middle where the full scale div should appear.
var viewport = {
x: $("#scroll").scrollLeft(),
width: $("#scroll").width(),
}
$("#scroll").scroll(function() {
viewport.x = $("#scroll").scrollLeft();
recalculateScale();
});
recalculateScale();
function recalculateScale() {
$("#example > div").each(function() {
let middleOfThis = ($(this).position().left + ($(this).width() * 0.5)); // calculate from the middle of each div
let scale = Math.sin(middleOfThis / $("content").width());
$(this).css('transform', 'scale(' + scale + ')');
});
}
content {
position: relative;
display: block;
width: 500px;
height: 100px;
}
content::after {
content: '';
position: absolute;
display: block;
width: 20%;
height: 100%;
left: 50%;
top: 0;
transform: translateX(-50%);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5);
}
#scroll {
display: block;
width: 100%;
height: 100%;
overflow-x: scroll;
border: 1px solid #000;
}
#example {
display: block;
width: 200%;
height: 100%;
font-size: 0;
overflow: none;
}
#example>div {
display: inline-block;
width: 10%;
height: 100%;
background-color: #f00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<content>
<div id="scroll">
<section id="example">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</section>
</div>
</content>
Currently the scale is spanning from far left to right of #example. I know I need to factor the viewport dimensions into the equation before Math.sin is evaluated, I just can't get it quite right.
Note: no arrow functions because I have to target IE11.
Two issues:
While .position().left returns the rendered position of the element after scaling, .width() returns the element's width without taking the scaling into account. Obviously such different way of measurement will lead to a wrong calculation of the middle point. Use .getBoundingClientRect().width instead: that will take the current scaling into account
When using trigonometric functions, you need to make sure the argument represents an angle expressed in radians. In your code, the value ranges from 0 to 1, while the sine takes its maximum value not at 0.5, but at π/2. So you should perform a multiplication with π to get the desired result.
Here is the adapted code:
var viewport = {
x: $("#scroll").scrollLeft(),
width: $("#scroll").width(),
}
$("#scroll").scroll(function() {
viewport.x = $("#scroll").scrollLeft();
recalculateScale();
});
recalculateScale();
function recalculateScale() {
$("#example > div").each(function() {
// 1. Use different way to read the width: this will give the rendered width
// after scaling, just like the left position will be the actually rendered
// position after scaling:
let middleOfThis = $(this).position().left
+ this.getBoundingClientRect().width * 0.5;
// 2. Convert fraction to a number of radians:
let scale = Math.sin(middleOfThis / $("content").width() * Math.PI);
$(this).css('transform', 'scale(' + scale + ')');
});
}
content {
position: relative;
display: block;
width: 500px;
height: 100px;
}
content::after {
content: '';
position: absolute;
display: block;
width: 20%;
height: 100%;
left: 50%;
top: 0;
transform: translateX(-50%);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5);
}
#scroll {
display: block;
width: 100%;
height: 100%;
overflow-x: scroll;
border: 1px solid #000;
}
#example {
display: block;
width: 200%;
height: 100%;
font-size: 0;
overflow: none;
}
#example>div {
display: inline-block;
width: 10%;
height: 100%;
background-color: #f00;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<content>
<div id="scroll">
<section id="example">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</section>
</div>
</content>
NB: Because of floating point precision limitations, the calculation of the mid points could slide away with little fractions. This will be so tiny, that it should not make a difference in actual pixel distance, but it would not hurt to pre-calculate the centres of the elements, so that you always use the same value.

Categories

Resources