CSS position elements on the outside of a circle [duplicate] - html

How can I position several <img> elements into a circle around another and have those elements all be clickable links as well? I want it to look like the picture below, but I have no idea how to achieve that effect.
Is this even possible?

2020 solution
Here's a more modern solution I use these days.
I start off by generating the HTML starting from an array of images. Whether the HTML is generated using PHP, JS, some HTML preprocessor, whatever... this matters less as the basic idea behind is the same.
Here's the Pug code that would do this:
//- start with an array of images, described by url and alt text
- let imgs = [
- {
- src: 'image_url.jpg',
- alt: 'image alt text'
- } /* and so on, add more images here */
- ];
- let n_imgs = imgs.length;
- let has_mid = 1; /* 0 if there's no item in the middle, 1 otherwise */
- let m = n_imgs - has_mid; /* how many are ON the circle */
- let tan = Math.tan(Math.PI/m); /* tangent of half the base angle */
.container(style=`--m: ${m}; --tan: ${+tan.toFixed(2)}`)
- for(let i = 0; i < n_imgs; i++)
a(href='#' style=i - has_mid >= 0 ? `--i: ${i}` : null)
img(src=imgs[i].src alt=imgs[i].alt)
The generated HTML looks as follows (and yes, you can write the HTML manually too, but it's going to be a pain to make changes afterwards):
<div class="container" style="--m: 8; --tan: 0.41">
<a href='#'>
<img src="image_mid.jpg" alt="alt text"/>
</a>
<a style="--i: 1">
<img src="first_img_on_circle.jpg" alt="alt text"/>
</a>
<!-- the rest of those placed on the circle -->
</div>
In the CSS, we decide on a size for the images, let's say 8em. The --m items are positioned on a circle and it's if they're in the middle of the edges of a polygon of --m edges, all of which are tangent to the circle.
If you have a hard time picturing that, you can play with this interactive demo which constructs the incircle and circumcircle for various polygons whose number of edges you pick by dragging the slider.
This tells us that the size of the container must be twice the radius of the circle plus twice half the size of the images.
We don't yet know the radius, but we can compute it if we know the number of edges (and therefore the tangent of half the base angle, precomputed and set as a custom property --tan) and the polygon edge. We probably want the polygon edge to be a least the size of the images, but how much we leave on the sides is arbitrary. Let's say we have half the image size on each side, so the polygon edge is twice the image size. This gives us the following CSS:
.container {
--d: 6.5em; /* image size */
--rel: 1; /* how much extra space we want between images, 1 = one image size */
--r: calc(.5*(1 + var(--rel))*var(--d)/var(--tan)); /* circle radius */
--s: calc(2*var(--r) + var(--d)); /* container size */
position: relative;
width: var(--s); height: var(--s);
background: silver /* to show images perfectly fit in container */
}
.container a {
position: absolute;
top: 50%; left: 50%;
margin: calc(-.5*var(--d));
width: var(--d); height: var(--d);
--az: calc(var(--i)*1turn/var(--m));
transform:
rotate(var(--az))
translate(var(--r))
rotate(calc(-1*var(--az)))
}
img { max-width: 100% }
See the old solution for an explanation of how the transform chain works.
This way, adding or removing an image from the array of images automatically arranges the new number of images on a circle such that they're equally spaced out and also adjusts the size of the container. You can test this in this demo.
OLD solution (preserved for historical reasons)
Yes, it is very much possible and very simple using just CSS. You just need to have clear in mind the angles at which you want the links with the images (I've added a piece of code at the end just for showing the angles whenever you hover one of them).
You first need a wrapper. I set its diameter to be 24em (width: 24em; height: 24em; does that), you can set it to whatever you want. You give it position: relative;.
You then position your links with the images in the center of that wrapper, both horizontally and vertically. You do that by setting position: absolute; and then top: 50%; left: 50%; and margin: -2em; (where 2em is half the width of the link with the image, which I've set to be 4em - again, you can change it to whatever you wish, but don't forget to change the margin in that case).
You then decide on the angles at which you want to have your links with the images and you add a class deg{desired_angle} (for example deg0 or deg45 or whatever). Then for each such class you apply chained CSS transforms, like this:
.deg{desired_angle} {
transform: rotate({desired_angle}) translate(12em) rotate(-{desired_angle});
}
where you replace {desired_angle} with 0, 45, and so on...
The first rotate transform rotates the object and its axes, the translate transform translates the object along the rotated X axis and the second rotate transform brings back the object into position.
The advantage of this method is that it is flexible. You can add new images at different angles without altering the current structure.
CODE SNIPPET
.circle-container {
position: relative;
width: 24em;
height: 24em;
padding: 2.8em;
/*2.8em = 2em*1.4 (2em = half the width of a link with img, 1.4 = sqrt(2))*/
border: dashed 1px;
border-radius: 50%;
margin: 1.75em auto 0;
}
.circle-container a {
display: block;
position: absolute;
top: 50%; left: 50%;
width: 4em; height: 4em;
margin: -2em;
}
.circle-container img { display: block; width: 100%; }
.deg0 { transform: translate(12em); } /* 12em = half the width of the wrapper */
.deg45 { transform: rotate(45deg) translate(12em) rotate(-45deg); }
.deg135 { transform: rotate(135deg) translate(12em) rotate(-135deg); }
.deg180 { transform: translate(-12em); }
.deg225 { transform: rotate(225deg) translate(12em) rotate(-225deg); }
.deg315 { transform: rotate(315deg) translate(12em) rotate(-315deg); }
<div class='circle-container'>
<a href='#' class='center'><img src='image.jpg'></a>
<a href='#' class='deg0'><img src='image.jpg'></a>
<a href='#' class='deg45'><img src='image.jpg'></a>
<a href='#' class='deg135'><img src='image.jpg'></a>
<a href='#' class='deg180'><img src='image.jpg'></a>
<a href='#' class='deg225'><img src='image.jpg'></a>
<a href='#' class='deg315'><img src='image.jpg'></a>
</div>
Also, you could further simplify the HTML by using background images for the links instead of using img tags.
EDIT: example with fallback for IE8 and older (tested in IE8 and IE7)

Here is the easy solution without absolute positioning:
.container .row {
margin: 20px;
text-align: center;
}
.container .row img {
margin: 0 20px;
}
<div class="container">
<div class="row">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
</div>
<div class="row">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
</div>
<div class="row">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
<img src="https://ssl.gstatic.com/s2/oz/images/faviconr2.ico" alt="" width="64" height="64">
</div>
</div>
http://jsfiddle.net/mD6H6/

Using the solution proposed by #Ana:
transform: rotate(${angle}deg) translate(${radius}px) rotate(-${angle}deg)
I created the following jsFiddle that places circles dynamically using plain JavaScript (jQuery version also available).
The way it works is rather simple:
document.querySelectorAll( '.ciclegraph' ).forEach( ( ciclegraph )=>{
let circles = ciclegraph.querySelectorAll( '.circle' )
let angle = 360-90, dangle = 360 / circles.length
for( let i = 0; i < circles.length; ++i ){
let circle = circles[i]
angle += dangle
circle.style.transform = `rotate(${angle}deg) translate(${ciclegraph.clientWidth / 2}px) rotate(-${angle}deg)`
}
})
.ciclegraph {
position: relative;
width: 500px;
height: 500px;
margin: calc(100px / 2 + 0px);
}
.ciclegraph:before {
content: "";
position: absolute;
top: 0; left: 0;
border: 2px solid teal;
width: calc( 100% - 2px * 2);
height: calc( 100% - 2px * 2 );
border-radius: 50%;
}
.ciclegraph .circle {
position: absolute;
top: 50%; left: 50%;
width: 100px;
height: 100px;
margin: calc( -100px / 2 );
background: teal;
border-radius: 50%;
}
<div class="ciclegraph">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>

Building off #Ana's excellent answer, I created this dynamic version that allows you to add and remove elements from the DOM and maintain proportionate spacing between the elements - check out my fiddle: https://jsfiddle.net/skwidbreth/q59s90oy/
var list = $("#list");
var updateLayout = function(listItems) {
for (var i = 0; i < listItems.length; i++) {
var offsetAngle = 360 / listItems.length;
var rotateAngle = offsetAngle * i;
$(listItems[i]).css("transform", "rotate(" + rotateAngle + "deg) translate(0, -200px) rotate(-" + rotateAngle + "deg)")
};
};
$(document).on("click", "#add-item", function() {
var listItem = $("<li class='list-item'>Things go here<button class='remove-item'>Remove</button></li>");
list.append(listItem);
var listItems = $(".list-item");
updateLayout(listItems);
});
$(document).on("click", ".remove-item", function() {
$(this).parent().remove();
var listItems = $(".list-item");
updateLayout(listItems);
});
#list {
background-color: blue;
height: 400px;
width: 400px;
border-radius: 50%;
position: relative;
}
.list-item {
list-style: none;
background-color: red;
height: 50px;
width: 50px;
position: absolute;
top: 50%;
left: 50%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<ul id="list"></ul>
<button id="add-item">Add item</button>

Here is a version I made in React from the examples here.
CodeSandbox Example
import React, { useRef, useEffect } from "react";
import "./styles.css";
export default function App() {
const graph = useRef(null);
useEffect(() => {
const ciclegraph = graph.current;
const circleElements = ciclegraph.childNodes;
let angle = 360 - 90;
let dangle = 360 / circleElements.length;
for (let i = 0; i < circleElements.length; i++) {
let circle = circleElements[i];
angle += dangle;
circle.style.transform = `rotate(${angle}deg) translate(${ciclegraph.clientWidth /
2}px) rotate(-${angle}deg)`;
}
}, []);
return (
<div className="App">
<div className="ciclegraph" ref={graph}>
<div className="circle" />
<div className="circle" />
<div className="circle" />
<div className="circle" />
<div className="circle" />
<div className="circle" />
</div>
</div>
);
}

You can certainly do it with pure css or use JavaScript. My suggestion:
If you already know that the images number will never change just calculate your styles and go with plain css (pros: better performances, very reliable)
If the number can vary either dynamically in your app or just may vary in the future go with a Js solution (pros: more future-proof)
I had a similar job to do, so I created a script and open sourced it here on Github for anyone who might need it. It just accepts some configuration values and simply outputs the CSS code you need.
If you want to go for the Js solution here's a simple pointer that can be useful to you. Using this html as a starting point being #box the container and .dot the image/div in the middle you want all your other images around:
Starting html:
<div id="box">
<div class="dot"></div>
<img src="my-img.jpg">
<!-- all the other images you need-->
</div>
Starting Css:
#box{
width: 400px;
height: 400px;
position: relative;
border-radius: 100%;
border: 1px solid teal;
}
.dot{
position: absolute;
border-radius: 100%;
width: 40px;
height: 40px;
left: 50%;
top: 50%;
margin-left: -20px;
margin-top: -20px;
background: rebeccapurple;
}
img{
width: 40px;
height: 40px;
position: absolute;
}
You can create a quick function along these lines:
var circle = document.getElementById('box'),
imgs = document.getElementsByTagName('img'),
total = imgs.length,
coords = {},
diam, radius1, radius2, imgW;
// get circle diameter
// getBoundingClientRect outputs the actual px AFTER transform
// using getComputedStyle does the job as we want
diam = parseInt( window.getComputedStyle(circle).getPropertyValue('width') ),
radius = diam/2,
imgW = imgs[0].getBoundingClientRect().width,
// get the dimensions of the inner circle we want the images to align to
radius2 = radius - imgW
var i,
alpha = Math.PI / 2,
len = imgs.length,
corner = 2 * Math.PI / total;
// loop over the images and assign the correct css props
for ( i = 0 ; i < total; i++ ){
imgs[i].style.left = parseInt( ( radius - imgW / 2 ) + ( radius2 * Math.cos( alpha ) ) ) + 'px'
imgs[i].style.top = parseInt( ( radius - imgW / 2 ) - ( radius2 * Math.sin( alpha ) ) ) + 'px'
alpha = alpha - corner;
}
You can see a live example here

There is no way to magically place clickable items in a circle around another element with CSS.
The way how I would do this is by using a container with position:relative;. And then place all the elements with position:absolute; and using top and left to target it's place.
Even though you haven't placed jquery in your tags it might be best to use jQuery / javascript for this.
First step is placing your center image perfectly in the center of the container using position:relative;.
#centerImage {
position:absolute;
top:50%;
left:50%;
width:200px;
height:200px;
margin: -100px 0 0 -100px;
}
After that you can place the other elements around it by using an offset() of the centerImage minus the offset() of the container. Giving you the exact top and left of the image.
var left = $('#centerImage').offset().left - $('#centerImage').parent().offset().left;
var top = $('#centerImage').offset().top - $('#centerImage').parent().offset().top;
$('#surroundingElement1').css({
'left': left - 50,
'top': top - 50
});
$('#surroundingElement2').css({
'left': left - 50,
'top': top
});
$('#surroundingElement3').css({
'left': left - 50,
'top': top + 50
});
What I've done here is placing the elements relative to the centerImage. Hope this helps.

You could do it like this: fiddle
Don't mind the positioning, its a quick example

The first step is to have 6 long columnar boxes:
The second step is to use position: absolute and move them all into the middle of your container:
And now rotate them around the pivot point located at the bottom center. Use :nth-child to vary rotation angles:
div {
transform-origin: bottom center;
#for $n from 0 through 7 {
&:nth-child(#{$n}) {
rotate: (360deg / 6) * $n;
}
}
Now all you have to do is to locate your images at the far end of every column, and compensate the rotation with an anti-rotation :)
Full source:
<div class="flower">
<div class="petal">1</div>
<div class="petal">2</div>
<div class="petal">3</div>
<div class="petal">4</div>
<div class="petal">5</div>
<div class="petal">6</div>
</div>
.flower {
width: 300px;
height: 300px;
// We need a relative position
// so that children can have "position:abolute"
position: relative;
.petal {
// Make sure petals are visible
border: 1px solid #999;
// Position them all in one point
position: absolute; top: 0; left: 50%;
display: inline-block;
width: 30px; height: 150px;
// Rotation
transform-origin: bottom center;
#for $n from 0 through 7 {
&:nth-child(#{$n}) {
// Petal rotation
$angle: (360deg / 6) * $n;
rotate: $angle;
// Icon anti-rotation
.icon { rotate: -$angle; }
}
}
}
}
See CodePen

Related

Custom onpage html based on selected slider image

Apologies if I incorrectly use some terminology here, or don't know the terms to properly describe this but...
Easy Part- I would like to create a Wheel style slider, displaying three pictures, with the main "selected" one being forefront and the other two sitting scaled down, behind them but quickly and easily clickable and viewable.
Hard Part- I would like which ever picture is set in the forefront main portion of the slider wheel to have information displayed, page width, regarding that specific toggled picture and that picture only. Then when you toggle to a different slide/picture ONLY information regarding that would then be placed below the slider/toggle wheel.
I would think my starting point would be to grab some code for the toggle/slider picture wheel. Then somehow create some sort of event trigger type coding for whichever picture is highlighted, coupling that with some sort of html hide/show coding.
I attached some bad sketches to help me visual depict what I am saying.
Any insight is welcome, even if it is some keywords to help me narrow down my google searching and find some resources. Slide & Page Layout Sketch
Thanks & Cheers
Sounds like you're going to need some CSS and JS to make this work.
First, you're going to need your HTML layout. I have a wrapper for the entire carousel (.container). I have the left and right arrows as well as a second wrapper for the images.
For the text below the carousel, I have a second element (.content) which holds three elements, each correlating to the images. The text is only shown when .shown is applied to the element.
<div class="container">
<div class="left"><</div>
<div>
<div class="img img-left"></div>
<div class="img img-center"></div>
<div class="img img-right"></div>
</div>
<div class="right">></div>
</div>
<div class="content">
<div class="text">
Amazing Sunset
</div>
<div class="text shown">
Fall Leaves
</div>
<div class="text">
Misty Sunlight
</div>
</div>
For CSS I choose to make the .container position: relative so that I could use position: absolute on the children. I have 3 classes for the images. img-left, img-right and img-center. These can be animated. The arrows are simply centered vertically
.container {
position: relative;
height: 85vh;
}
.img {
position: absolute;
z-index: 2;
height: 75vh;
width: 121vh;
max-height: 300px;
max-width: 511px;
top: 0; bottom: 0;
left: 0; right: 0;
margin: auto;
transition: transform 0.3s, z-index 0s linear 0.15s;
}
.img:nth-of-type(1) {
background: url('https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg') center/contain no-repeat;
}
.img:nth-of-type(2) {
background: url('https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823__340.jpg') center/contain no-repeat;
}
.img:nth-of-type(3) {
background: url('https://cdn.pixabay.com/photo/2015/09/09/16/05/forest-931706__340.jpg') center/contain no-repeat;
}
.img-center {
z-index: 5;
}
.img-right {
transform: translateX(200px) scale(0.7);
}
.img-left {
transform: translateX(-200px) scale(0.7);
}
.left, .right {
position: absolute;
z-index: 7;
top: 50%;
font-size: 48px;
font-family: monospace;
transform: translateY(-50%);
user-select: none;
}
.left {
left: 32px;
}
.right {
right: 32px;
}
.content {
height: 15vh;
}
.text {
display: none;
text-align: center;
font-size: 32px;
}
.text.shown {
display: block;
}
JavaScript is where things start to get more interesting. I created a function called nextImage() which takes a boolean representing the direction to switch. First, it gets the currently centered image and then based on that image gets the next and previous element siblings. In the event that the centered image happens to be the first or last element, either next or pre will be undefined. That is handled next. Once that is done the CSS classes are reassigned based on the direction.
function nextImage(forward) {
let currentCentered = document.querySelector('.img-center'),
next = currentCentered.nextElementSibling,
pre = currentCentered.previousElementSibling;
//pre and next may not be elements if currentCentered is the frist or last element.
if (!next) { //Centered Element is the frist
next = pre.previousElementSibling;
} else if (!pre) { //Centered Element is the last
pre = next.nextElementSibling;
}
if (forward) {
//Move the previously centered image to the right
currentCentered.classList.remove('img-center');
currentCentered.classList.add('img-right');
//Move the previously left image to the center
pre.classList.remove('img-left');
pre.classList.add('img-center');
//Move the previously right image to the left
next.classList.remove('img-right');
next.classList.add('img-left');
} else {
//Move the previously centered image to the left
currentCentered.classList.remove('img-center');
currentCentered.classList.add('img-left');
//Move the previously left image to the right
pre.classList.remove('img-left');
pre.classList.add('img-right');
//Move the previously right image to the center
next.classList.remove('img-right');
next.classList.add('img-center');
}
//Update the text
let currentText = document.querySelector('.text.shown'),
newText;
if (forward) {
//Get the previous element;
newText = currentText.previousElementSibling;
//If it doesn't exist get the last element
if (!newText) {
newText = currentText.nextElementSibling.nextElementSibling;
}
} else {
//Get the next element;
newText = currentText.nextElementSibling;
//If it doesn't exist get the frist element
if (!newText) {
newText = currentText.previousElementSibling.previousElementSibling;
}
}
//Apply class change
currentText.classList.remove('shown');
newText.classList.add('shown');
}
Adding onclick="nextImage(false)" to the left arrow and onclick="nextImage(true)" to the right arrow now lets you navigate with them.
Since you said you'd like the pictures so be clickable I've added a second function which lets you scroll to the given image. It gets the next and previous elements and makes sure they are actual elements. Then it called the nextImage() function based on if next or pre is the centered image.
function switchImage(imgEle) {
let next = imgEle.nextElementSibling,
pre = imgEle.previousElementSibling;
//Make sure they are actually elements
if (!next) {
next = pre.previousElementSibling;
}
if (!pre) {
pre = next.nextElementSibling;
}
if (next.classList.contains('img-center')) {
nextImage(true);
} else if (pre.classList.contains('img-center')) {
nextImage(false);
}
}
All you have to do now is add onclick="switchImage(this)" to each of your image elements.
Adding this all together you should get something like this snippet below
function nextImage(forward) {
let currentCentered = document.querySelector('.img-center'),
next = currentCentered.nextElementSibling,
pre = currentCentered.previousElementSibling;
//pre and next may not be elements if currentCentered is the frist or last element.
if (!next) { //Centered Element is the frist
next = pre.previousElementSibling;
} else if (!pre) { //Centered Element is the last
pre = next.nextElementSibling;
}
if (forward) {
//Move the previously centered image to the right
currentCentered.classList.remove('img-center');
currentCentered.classList.add('img-right');
//Move the previously left image to the center
pre.classList.remove('img-left');
pre.classList.add('img-center');
//Move the previously right image to the left
next.classList.remove('img-right');
next.classList.add('img-left');
} else {
//Move the previously centered image to the left
currentCentered.classList.remove('img-center');
currentCentered.classList.add('img-left');
//Move the previously left image to the right
pre.classList.remove('img-left');
pre.classList.add('img-right');
//Move the previously right image to the center
next.classList.remove('img-right');
next.classList.add('img-center');
}
//Update the text
let currentText = document.querySelector('.text.shown'),
newText;
if (forward) {
//Get the previous element;
newText = currentText.previousElementSibling;
//If it doesn't exist get the last element
if (!newText) {
newText = currentText.nextElementSibling.nextElementSibling;
}
} else {
//Get the next element;
newText = currentText.nextElementSibling;
//If it doesn't exist get the frist element
if (!newText) {
newText = currentText.previousElementSibling.previousElementSibling;
}
}
//Apply class change
currentText.classList.remove('shown');
newText.classList.add('shown');
}
function switchImage(imgEle) {
let next = imgEle.nextElementSibling,
pre = imgEle.previousElementSibling;
//Make sure they are actually elements
if (!next) {
next = pre.previousElementSibling;
}
if (!pre) {
pre = next.nextElementSibling;
}
if (next.classList.contains('img-center')) {
nextImage(true);
} else if (pre.classList.contains('img-center')) {
nextImage(false);
}
}
.container {
position: relative;
height: 85vh;
}
.img {
position: absolute;
z-index: 2;
height: 75vh;
width: 121vh;
max-height: 300px;
max-width: 511px;
top: 0; bottom: 0;
left: 0; right: 0;
margin: auto;
transition: transform 0.3s, z-index 0s linear 0.15s;
}
.img:nth-of-type(1) {
background: url('https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__340.jpg') center/contain no-repeat;
}
.img:nth-of-type(2) {
background: url('https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823__340.jpg') center/contain no-repeat;
}
.img:nth-of-type(3) {
background: url('https://cdn.pixabay.com/photo/2015/09/09/16/05/forest-931706__340.jpg') center/contain no-repeat;
}
.img-center {
z-index: 5;
}
.img-right {
transform: translateX(200px) scale(0.7);
}
.img-left {
transform: translateX(-200px) scale(0.7);
}
.left, .right {
position: absolute;
z-index: 7;
top: 50%;
font-size: 48px;
font-family: monospace;
transform: translateY(-50%);
user-select: none;
}
.left {
left: 32px;
}
.right {
right: 32px;
}
.content {
height: 15vh;
}
.text {
display: none;
text-align: center;
font-size: 32px;
}
.text.shown {
display: block;
}
<div class="container">
<div class="left" onclick="nextImage(false)"><</div>
<div>
<div class="img img-left" onclick="switchImage(this)"></div>
<div class="img img-center" onclick="switchImage(this)"></div>
<div class="img img-right" onclick="switchImage(this)"></div>
</div>
<div class="right" onclick="nextImage(true)">></div>
</div>
<div class="content">
<div class="text">
Amazing Sunset
</div>
<div class="text shown">
Fall Leaves
</div>
<div class="text">
Misty Sunlight
</div>
</div>

Can animation only be applied to 'absolute' positioned elements?

I am trying to animate an object using DOM and struggling to animate the element when its CSS property position is not set to "absolute". Here is my code below:
I create a circle HTML element and try to move it in 45 degrees. Is there any way to animate an HTML element object that is not positioned absolute?
x = 10;
function on_click() {
var myCurvyMovement = document.getElementById("circle");
myCurvyMovement.style.left = 0.5 * x;
myCurvyMovement.style.top = 1 + x
x += 10;
}
#circle {
position: absolute;
width: 100px;
height: 100px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
}
/* Cleaner, but slightly less support: use "50%" as value */
#divBox {
position: static
}
<body>
<button style="display:block" onclick="on_click()">Move the box</button>
<div id="circle">
</div>
</body>
I wouldn't consider left/right in order to do animation. As you have noticed, it won't work in all the cases as it need positionned elements. Even when using positionned element you won't have the same behavior between relative, absolute and fixed because each one will have its own reference for top/left.
For such case better consider transform that you can apply to any element (shouldn't be an inline element) and the reference of the movement will be the same for all. You will also have better performance.
x = 10;
function on_click() {
var myCurvyMovement = document.getElementById("circle");
myCurvyMovement.style.transform = "translate(" + (0.5 * x)+"px,"+(1 + x)+"px)";
x += 10;
}
#circle {
width: 100px;
height: 100px;
background: red;
border-radius: 50px;
transition:0.5s all; /*to have a smooth movement*/
}
<body>
<button style="display:block" onclick="on_click()">Move the box</button>
<div id="circle">
</div>
</body>
You forgot to concatenate the "px" to set the x and y positions
x = 10;
function on_click() {
var myCurvyMovement = document.getElementById("circle");
myCurvyMovement.style.left = 0.5 * x + 'px';
myCurvyMovement.style.top = 1 + x + 'px';
x += 10;
}
#circle {
position: absolute;
width: 100px;
height: 100px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
}
/* Cleaner, but slightly less support: use "50%" as value */
#divBox {
position: static
}
<body>
<button style="display:block" onclick="on_click()">Move the box</button>
<div id="circle">
</div>
</body>
when not's absolute you need change the margin-left and margin-top property, in javascript is like this
myCurvyMovement.style.marginLeft = 1 + x + 'px'
myCurvyMovement.style.marginTop = 1 + x + 'px'
(top/bottom and left/rigth)

How to use negative padding in css

I want to add negative padding in css, I have written a small code of battery charging cell. What I want is if I enter value in negative like -1px than the cell color should move to the left side and div should stay in center.
.cell {
width: 100px;
height: 30px;
border: 1px solid black;
}
.padding {
background-color: #3D9970;
width: 10px;
float: left;
height: 30px;
position: absolute;
left: 55px;
padding-right: 1px;
}
<div class="cell">
<div class="cell1"></div>
<div class="padding"></div><span style="display: inline;">
</div>
Please help me.
You can't.
See the specification:
Unlike margin properties, values for padding values cannot be negative.
I think you can achieve the same effect with pseudo elements:
.cell{
display:block;
width: 100px;
height: 30px;
position:relative;
}
.cell:before{
content:'';
background-color: #3D9970;
width: 10px;
top:0;
left:calc(50% - 5px);
height: 100%;
position: absolute;
}
.cell:after{
content:'';
border: 1px solid black;
width:100%;
height:100%;
display:block;
top:0;
left: 0px;
position: absolute;
}
<div class="cell">
</div>
"Left" property could be negative, so if you change it you can move the position of the green rectangle in the middle (.cell:before) of the block and border itself (.after)
The easiest way is to use an absolute positioning relatively to a parent node. Here the parent node would be the battery "housing".
So you can set the position CSS value of the rot div to relative, and then the charge one to absolute. Indeed, according to MDN Webdocs:
absolute: [...] It is positioned relative to its closest positioned ancestor, if any.
Then, you just have to play with the left and width CSS properties. For the "middle" case, I chose to display one border.
Below a working snippet. Just click the "Begin the charge variation" button to start the show.
var chargeElement = document.getElementById("charge");
// To set a charge to the battery, simply call: setCharge(percentage)
function setCharge(percentage) {
var left;
var width;
if (percentage > 100) percentage = 100;
if (percentage < 0) percentage = 0;
chargeElement.setAttribute("data-value", percentage);
// If the charge is 50%, simply draw a line
if (percentage == 50) {
chargeElement.className = "middle";
} else {
chargeElement.className = "";
}
// Otherwise, adjust left and width values
if (percentage >= 50) {
left = 50;
width = percentage - left;
} else {
left = percentage;
width = 50 - left;
}
// Then update the charge style.
chargeElement.style.left = left + "%";
chargeElement.style.width = width + "%";
}
// A simple function to add / remove some charge
function addCharge(percentage) {
var value = parseInt(chargeElement.getAttribute("data-value"));
value += percentage;
setCharge(value);
}
// Here just some stuff for illustration.
// You don't need those functions to set the charge.
function letsBeginTheShow(buttonElement) {
buttonElement.disabled = true;
setNextCharge(10);
}
function setNextCharge(increment) {
var percentage = parseInt(chargeElement.getAttribute("data-value"))
percentage += increment;
if (percentage > 100) {
percentage = 100;
increment = -5;
}
if (percentage < 0) {
percentage = 0;
increment = 5;
}
setCharge(percentage);
setTimeout(function() {
setNextCharge(increment);
}, 50);
}
setCharge(50);
.battery {
position: relative;
width: 100px;
height: 30px;
border: 1px solid black;
/* Below : only for aestethic reasons */
float: left;
margin-right: 30px;
/* End of aesthethic stuff */
}
#charge {
height: 100%;
position: absolute;
background-color: #3D9970;
border-color: #3D9970;
}
.middle {
border-left: 1px solid;
}
<div class="battery">
<div id="charge" data-value="50" class="middle"></div>
</div>
<button onclick="letsBeginTheShow(this)">Begin the charge variation</button>

Dynamically Scaling SVG

I'm working on a site where users can manipulate an SVG image through a couple of textboxes.
I would like to have the SVG scale to fit the container div.
For example, if the SVG was exactly the container's height and 10 pixels wide, then doubling the height would cause the apparent width to be 5 pixels.
My page is split roughly in half, with the numbers on the left and the image on the right. Resizing the browser thus causes the SVG's container element to change shape, meaning that I can't hardcode the container's dimensions in the SVG.
Every solution I've found online uses the viewBox attribute; however, I can't find a way to apply that without having a hard-coded container size.
Here is a fiddle with my editor setup:
https://jsfiddle.net/xyjs5b63/
Adjusting viewBox sounds like what you want. I'm not sure what you were doing that made it not work.
var svg = document.querySelector('svg');
var inputs = document.querySelectorAll('input');
var height_elem = inputs[0];
var width_elem = inputs[1];
height_elem.value = '100';
width_elem.value = '100';
height_elem.addEventListener("change", valueChange);
width_elem.addEventListener("change", valueChange);
function valueChange() {
svg.setAttribute('viewBox', "0 0 "+width_elem.value+" "+height_elem.value);
}
valueChange();
#out {
width: 100px;
height: 100px;
background-color: honeydew;
}
svg {
width: 100%;
height: 100%;
}
<div id="main">
<div id="in">
<input type="number"><br>
<input type="number">
</div>
<div id="out">
<svg>
<rect width="100%" height="100%"></rect>
</svg>
</div>
</div>
var rect = document.querySelector('rect');
var svg = document.querySelector('svg');
var inputs = document.querySelectorAll('input');
var height_elem = inputs[0];
var width_elem = inputs[1];
height_elem.value = '100';
width_elem.value = '100';
height_elem.addEventListener("change", valueChange);
width_elem.addEventListener("change", valueChange);
function valueChange() {
max = parseInt(height_elem.value) >= parseInt(width_elem.value) ? 'h' : 'w';
if (max == 'h') {
rect.setAttribute('height', "100%");
rect.setAttribute('width', (width_elem.value * 100 / height_elem.value)+"%");
}
else {
rect.setAttribute('width', "100%");
rect.setAttribute('height', (height_elem.value * 100 / width_elem.value)+"%");
}
}
valueChange();
#main {
width: 100%;
padding: 0;
}
#in {
float: left;
width: 40%;
height: 100%
}
#out {
margin: 10%;
width: 20vw;
height: 20vw;
}
svg {
width: 100%;
height: 100%;
}
<div id="main">
<div id="in">
Height: <input type="number"><br>
Width: <input type="number">
</div>
<br>
<div id="out">
<svg height="auto">
<rect></rect></svg>
</div>
</div>
Does this solve your problem?

Transform scale keeps the original space around the scaled element

I have two nested divs. The inner one is transform: scale(0.5).
Both are display: inline-block;.
What I need to happen is the outer div fits it's width to the width of the inner one. That's what I supposed to happen but not. What occur is that the outer div «thinks» the inner div has it's original size.
The outer div fits it's width to the inner's width only if the inner div is transform: scale(1) but not using an scale factor less than 1, for example: 0.5 (see example).
I need some way to achieve this by CSS in an elegant way.
.red {
background-color: #f00;
}
.green {
background-color: #0f0;
}
.box_1,
.box_2 {
display: inline-block;
}
.box_1 {
width: 300px;
height: 300px;
transform: scale(0.5);
transform-origin: left top;
}
<div class="box_2 green">
<div class="box_1 red">Hello World</div>
</div>
Any idea on how to solve this?
A brutal way would be to virtually reduce space needed by element.
Your example shows a known width & height, so it makes it easy. else you would need a javascript method.
.box_1 {
width: 300px;
height: 300px;
transform: scale(0.5);
transform-origin: left top;
margin-bottom:-150px;
margin-right:-150px;
}
https://jsfiddle.net/0bc4sxk3/1/
Scaling up would mean positive margins.
Transform only happens at screen, elements still use initial room and place needed in the flow of the document.
I think that one solution is to wrap the scaled-down element into an element with overflow: hidden.
The wrapper should have the exact dimensions of the scaled-down content.
This solution was best for me.
.wrapper {
width: 150px;
height: 150px;
overflow: hidden;
}
.content {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
transform: scale(0.5);
transform-origin: left top;
}
.box_1,
.box_2 {
display: inline-block;
}
.red {
background-color: #f00;
}
.green {
background-color: #0f0;
}
<div class="box_2 green">
<div class="box_1 red">Hello World</div>
</div>
Coming late to the party, but another way is to use a sizing element that is empty, not scaled, has the same external size as the scaled down element and sits underneath the scaled element. This drives the sizing of the parent, and the scaled element is then positioned absolutely on top of the sizing element.
.red { background-color: #f00; }
.green { background-color: #0f0; }
.blue { background-color: #00f; }
.container {
position: relative;
display: inline-block;
}
.sizingBox {
width: 150px;
height: 150px;
}
.content {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
transform: scale(0.5);
transform-origin: left top;
}
<div class="container green">
<div class="sizingBox blue"></div>
<div class="content red">Hello World</div>
</div>
If someone is looking for a copy-pasta React Componet, this seems to work based on Guy's code:
import * as React from "react";
interface Props
extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
scale?: number;
style?: React.CSSProperties;
fullHeight: number;
fullWidth: number;
}
export const ScaleBox: React.FC<Props> = ({
scale = 1,
style,
fullWidth,
fullHeight,
children,
...rest
}) => {
return (
<div
data-comment={"ScaleBox Container"}
style={{ position: "relative", display: "inline-block", ...style }}
{...rest}
>
<div
data-comment={"ScaleBox Sizing Box"}
style={{ width: fullWidth * scale, height: fullHeight * scale }}
></div>
<div
data-comment={"ScaleBox Content"}
style={{
transform: `scale(${scale})`,
transformOrigin: "top left",
position: "absolute",
top: 0,
left: 0,
}}
>
{children}
</div>
</div>
);
};
Even later, I've built on mikeysee's React component and written one that works with content that sizes dynamically (it uses negative margins to avoid resizing the children's content):
import * as React from 'react';
import useResizeObserver from '#react-hook/resize-observer';
interface Props
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {
scale?: number;
style?: React.CSSProperties;
}
/**
* The ScaleBox is an element that scales its content using CSS transform scale
* and makes sure the flow around the box is as if the box had the size
* according to the applied scale.
*
* The parent element of a ScaleBox must have the overflow: 'hidden' style.
*/
export const ScaleBox: React.FC<Props> = ({ scale = 1, style, children }) => {
const [marginX, setMarginX] = React.useState('0px');
const [marginY, setMarginY] = React.useState('0px');
const divRef = React.useRef<HTMLDivElement>(null);
useResizeObserver(divRef, (target) => {
setMarginX(`${(scale - 1) * target.contentRect.width}px`);
setMarginY(`${(scale - 1) * target.contentRect.height}px`);
});
React.useEffect(() => {
if (divRef.current) {
setMarginX(`${(scale - 1) * divRef.current.offsetWidth}px`);
setMarginY(`${(scale - 1) * divRef.current.offsetHeight}px`);
}
}, [scale]);
return (
<div
ref={divRef}
style={{
...style,
transform: `scale(${scale})`,
transformOrigin: 'top left',
marginRight: marginX,
marginBottom: marginY
}}
>
{children}
</div>
);
};