CSS imageviewer: Image rotation (and scaling) - javascript

I'm trying to have a simplistic image viewer in a react project of mine.
Is there a way to implement rotation (and ideally scaling) without referencing the current dimensions of the DOM node in JS?
So far I tried something like this (reduced case/taken from my jsfiddle):
function getTransform() {
switch (rotation) {
case 90: return {
transform: 'translateY(-100%) rotate(90deg)',
'transform-origin': 'bottom left'
};
case 180: return {
transform: 'translate(100%, -100%) rotate(180deg)',
'transform-origin': 'bottom left'
}
case 270: return {
transform: 'translateX(-100%) rotate(270deg)',
'transform-origin': 'top right'
};
default: return {
transform: 'none'
}
}
}
and apply that as styles on my element. Unfortunately that doesn't work. While I can use the 100%/relative values for Y (i.e. 90 degrees works, 180 degrees works for the Y axis at least), I cannot use it to translate the X axis - parts of the image are offscreen.
Demo: https://jsfiddle.net/7huLa8e1/2/
Is there a way to use relative values alone or do I have to grab the DOM node and use width/height in absolute pixel values to make this work?

If I understand your question correctly, your problem is that the image overflows the container to the right in an unspecified size.
We need to get around this. My idea is to float right the image inside the container. we will need a clearfix to keep it taking the correct size.
I have redone the jquery to works with css classes, since it is the easiest way to modify the child (the image)
var rotation = 0;
function rotate(deg) {
rotation = (360 + rotation + deg) % 360;
$('#image-viewer').attr('class', 'rotate' + rotation);
}
$(function(){
$('#rotateLeft').click(
function(event) {
rotate(-90);
}
);
$('#rotateRight').click(
function(event) {
rotate(90);
}
);
});
#image-viewer {
position: relative;
cursor: pointer;
border: solid red 2px;
}
.image-viewer-root {
height: 100vh;
}
.image-viewer-viewport {
height: 90%;
overflow: auto;
}
.rotate90 {
transform: rotate(90deg) translate(0%, -100%);
transform-origin: left top;
}
.rotate180 {
transform: rotate(180deg);
transform-origin: center center;
}
.rotate180 img {
float: right;
}
.rotate270 {
transform: translate(-100%, 0%) rotate(270deg);
transform-origin: right top;
}
.rotate270 img {
float: right;
}
.clearfix {
clear: both;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="image-viewer-root">
<nav class="toolbar navbar navbar-dark bg-faded">
<div class="nav navbar-nav">
<button id="rotateLeft" class="btn nav-item">RotateLeft</button>
<button id="rotateRight" class="btn nav-item">RotateRight</button>
</div>
</nav>
<div class="image-viewer-viewport">
<div id="image-viewer">
<img src="https://sstatic.net/stackexchange/img/logos/so/so-logo.png?v=9c558ec15d8a">
<div class="clearfix"></div>
</div>
</div>
</div>

Related

CSS position elements along ring of a circle [duplicate]

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

div scale transform problems

I am using a scale transform on a div so that I can use child elements with set positions and sizes and still scale to fill the screen. I am writing in GWT 2.7 and am having problems changing element visibility in Chrome and Safari (webkit bug?) on a Mac. Here's an example:
<!doctype html>
<html lang="en">
<head>
<style>
#container { position: absolute; left: 50px; top: 30px; width: 576px; height: 456px; overflow: hidden; background-color: cyan; }
#backCanvas { position: absolute; left: 44px; top: 260px; }
#frontCanvas { position: absolute; left: 240px; top: 397px; }
.scaled
{
-ms-transform-origin: 0 0; /* IE 9 */
-ms-transform: scale(1.2);
-webkit-transform-origin: 0 0; /* Safari 8 */
-webkit-transform: scale(1.2);
transform-origin: 0 0;
transform: scale(1.2);
}
</style>
</head>
<body onload="initCanvas()">
<div id="container" class="">
<canvas id="backCanvas" tabindex="-1" width="490" height="158"></canvas>
<canvas id="frontCanvas" tabindex="-1" width="87" height="31"></canvas>
<button onclick="showScaled(false)">Unscaled</button>
<button onclick="showScaled(true)">Scale transform</button>
<button onclick="showElement('frontCanvas',false)">Hide canvas</button>
<button onclick="showElement('frontCanvas',true)">Show canvas</button>
</div>
<script>
function initCanvas()
{
fillCanvas(document.getElementById("backCanvas"), "green");
fillCanvas(document.getElementById("frontCanvas"), "red");
}
function fillCanvas(canvas, color)
{
var context = canvas.getContext("2d");
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
}
function showScaled(scaled)
{
document.getElementById("container").className = scaled ? "scaled" : "";
}
function showElement(element, show)
{
// none of these work when scaled
document.getElementById(element).style.display = show ? null : "none";
// document.getElementById(element).style.visibility = show ? "visible" : "hidden";
// document.getElementById(element).style.zIndex = show ? "100" : "-100";
// document.getElementById(element).style.left = show ? "240px" : "-1000px";
}
</script>
</body>
</html>
The problem has to do with overflow:hidden. I included that in the container because that's what GWT does. So, I removed overflow from the container and added overflow:hidden to the body. This works for my app because I scale to fit the window. There may be some clipping problems with the container, but now the draw order works correctly.
This is a webKit bug that is in the process of being fixed.

Firefox CSS rotation differs from Chrome rotation

I want to make a 3D rectangle (parallelepiped) which the users can move with the arrows. It works fine in Chrome, but in Firefox some transitions (a lot actually) are different from Chrome. Look at this fiddle (this is my whole code) and compare it in both browsers to understand better.
Because the first fiddle contains a lot of code, I'll simplify it and pick one random strange transition. Look at this fiddle, and press the "Left" button or the left arrow one time. It works fine, but when you press it again, the rectangle rotates 3 times instead of 1 time.
Is this a Firefox bug or what am I doing wrong?
The code below is what you'll find in the simplified fiddle.
var position = 'show-front';
$('#left').bind('click', function() {
if (position == 'show-front') {
$('#box').removeClass().addClass('show-right');
position = 'show-right';
} else if (position == 'show-right') {
$('#box').removeClass().addClass('show-back-3');
position = 'show-back-3';
} else if (position == 'show-back-3') {
$('#box').removeClass().addClass('show-left');
position = 'show-left';
} else if (position == 'show-left') {
$('#box').removeClass().addClass('show-front');
position = 'show-front';
}
});
$(window).bind('keyup', function(event) {
switch (event.keyCode) {
case 37: // left
$('#left').click();
break;
}
});
.container {
width: 150px;
height: 100px;
position: relative;
margin: 25px auto 25px auto;
perspective: 600px;
}
#box {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
transition: transform 1s;
}
#box figure {
display: block;
position: absolute;
border: 1px solid black;
line-height: 98px;
font-size: 45px;
text-align: center;
font-weight: bold;
color: white;
}
figure {
margin: 0;
}
#box .front,
#box .back {
width: 148px;
height: 98px;
}
#box .right,
#box .left {
width: 48px;
height: 98px;
left: 50px;
}
#box .top,
#box .bottom {
width: 148px;
height: 48px;
top: 25px;
line-height: 48px;
}
#box .front {
background: hsla(000, 100%, 50%, 0.7);
}
#box .back {
background: hsla(160, 100%, 50%, 0.7);
}
#box .right {
background: hsla(120, 100%, 50%, 0.7);
}
#box .left {
background: hsla(180, 100%, 50%, 0.7);
}
#box .top {
background: hsla(240, 100%, 50%, 0.7);
}
#box .bottom {
background: hsla(300, 100%, 50%, 0.7);
}
#box .front {
transform: translateZ(25px);
}
#box .back {
transform: rotateX(180deg) translateZ(25px);
}
#box .right {
transform: rotateY(90deg) translateZ(75px);
}
#box .left {
transform: rotateY(-90deg) translateZ(75px);
}
#box .top {
transform: rotateX(90deg) translateZ(50px);
}
#box .bottom {
transform: rotateX(-90deg) translateZ(50px);
}
#box.show-front {
transform: translateZ(-50px);
}
#box.show-right {
transform: translateZ(-150px) rotateY(-90deg);
}
#box.show-back-3 {
transform: translateZ(-50px) rotateX(180deg) rotateZ(-180deg);
}
#box.show-left {
transform: translateZ(-150px) rotateY(90deg);
}
<section class="container">
<div id="box" class="show-front">
<figure class="front">1</figure>
<figure class="back">2</figure>
<figure class="right">3</figure>
<figure class="left">4</figure>
<figure class="top">5</figure>
<figure class="bottom">6</figure>
</div>
</section>
Based on the assumption that Firefox is just buggy in this regard (see analysis below), here is a workaround that works on Firefox. It wraps the #box element in another div, and only transitions the wrapper. And the wrapper is only ever rotated 90 degrees from the starting point in one direction at a time, so Firefox can't mess it up.
Once the transition finishes, the rotation is reset back to the starting position and simultaneously the inner box is rotated to the new position, both without transition, so the change is not visible.
The second important change is using the current computed transformation of #box and adding the rotation to that, so that we don't have to keep track of the rotations as we go.
Note that the order of rotations matters. To achieve what you're trying to do (rotating in "world space" rather than "object space"), you need to apply the rotations in reverse order. E.g. to rotate "right", use .css("transform", "rotateY(90deg) " + currentComputedTransform). This will resolve the issue you mentioned in comments where it appears to rotate around the wrong axis. See below for more information.
Note also that I don't allow a rotation to start if there's already one in progress, because that won't work. You could queue up keystrokes in an array if you want to be able to that, but you might also want to reduce the transition duration proportional to queue length in that case so it doesn't take forever.
Updated fiddle: https://jsfiddle.net/955k5fhh/7/
Relevant javascript:
$("#box").wrap("<div id='outer'></div>");
var pending=null;
function rotate(axis,angle,dir) {
if (pending) return;
$("#outer").removeClass().addClass(dir);
var current=$("#box").css("transform");
if (current=='none') current='';
pending="rotate"+axis+"("+angle+"deg) "
+ current;
}
$("#outer").bind('transitionend', function() {
$(this).removeClass();
$("#box").css('transform',pending);
pending=null;
});
$('#up').bind('click', function() {
rotate('X',90,"up");
});
$('#down').bind('click', function() {
rotate('X',-90,"down");
});
$('#right').bind('click', function() {
rotate('Y',90,"right");
});
$('#left').bind('click', function() {
rotate('Y',-90,"left");
});
Previous analysis
I've been playing with JS-based solutions and I came across this useful post https://gamedev.stackexchange.com/a/67317 - it points out that to rotate objects in "world space" instead of "object space", you just need to reverse the order of the rotations.
Based on that, I simplified your fiddle to the following:
var rot = "";
var tr = "translateZ(-50px)";
$('#up').bind('click', function() {
rot=" rotateX(90deg)"+rot;
$("#box").css("transform",tr+rot);
});
$('#down').bind('click', function() {
rot=" rotateX(-90deg)"+rot;
$("#box").css("transform",tr+rot);
});
$('#right').bind('click', function() {
rot=" rotateY(90deg)"+rot;
$("#box").css("transform",tr+rot);
});
$('#left').bind('click', function() {
rot=" rotateY(-90deg)"+rot;
$("#box").css("transform",tr+rot);
});
https://jsfiddle.net/955k5fhh/ (note that it's not a complete solution, because eventually the rot string will get too long)
And on Chrome, that behaves as expected. And once again, Firefox gets it wrong, even if you're just chaining e.g. a sequence of rotateX(90deg) transformations.
So I went one step further and rolled up adjacent rotations in the same axis...
var rots = [];
var tr = "translateZ(-50px)";
function transform() {
var tf = "translateZ(-50px)";
rots.forEach(function(rot) {
tf += " rotate" + rot[0] + "(" + rot[1] + "deg)";
});
console.log(tf);
$("#box").css("transform", tf);
}
function addRot(axis,angle) {
if (rots.length==0 || rots[0][0]!=axis) {
rots.unshift([axis,angle]);
} else {
rots[0][1]+=angle;
}
transform();
}
$('#up').bind('click', function() {
addRot('X',90);
});
$('#down').bind('click', function() {
addRot('X',-90);
});
$('#right').bind('click', function() {
addRot('Y',90);
});
$('#left').bind('click', function() {
addRot('Y',-90);
});
https://jsfiddle.net/955k5fhh/2/
Which, again, works well in Chrome, and works a bit better in Firefox, but still once you switch axes, you can wind up spinning the wrong way. And similarly if you click a button before a transition completes, it can spin the wrong way.
So I would conclude that unfortunately yes, Firefox is just buggy in this, but at least there are workarounds.
It looks like you are doing everything right, and the differences in rotation in Chrome vs Firefox is caused by the ways the two browsers process CSS3. When looking at the rotation from show-back-4 to show-top-4, your CSS file specifies the rotation to be 270deg. In Firefox, it does just that. In Chrome, it looks like it optimizes and doesn't do the full rotation, saving on processing power or something. So yeah, I think that it's just a difference in the browsers, not a bug in either one of them.
You could try using keyframes to get more control for the animation, something like this:
https://jsfiddle.net/L36v50kh/2/
I'm defining both starting and ending point for all transitions in the fiddle like this:
#keyframes front-to-right {
from {transform: translateZ(-50px) rotateY(0deg); }
to {transform: translateZ(-150px) rotateY(-90deg);}
}
That looks the same in both browsers but it's jumpy when clicking the button before the animation finishes.
You might also consider animating with JavaScript to get exact control and avoid defining every transition, something like this:
var applyRotation = function() {
$('#box').css('transform', 'rotateY(' + rotateY + 'deg)');
handleMultipleRotations();
};
var unwindTimeout;
var rotateY = 0;
var handleMultipleRotations = function() {
$('#box').css('transition-duration', '');
if (typeof unwindTimeout === 'number') {
clearTimeout(unwindTimeout);
unwindTimeout = undefined;
}
if (Math.abs(rotateY) >= 360) {
unwindTimeout = setTimeout(function() {
rotateY -= Math.floor(rotateY / 360) * 360;
$('#box').css({
'transition-duration': '0s',
'transform': 'rotateY(' + rotateY + 'deg)'
});
}, 1000);
}
};
$('document').ready(function() {
$('#left').on('click', function() {
rotateY -= 90;
applyRotation();
});
$('#right').on('click', function() {
rotateY += 90;
applyRotation();
});
});
/* minified to draw focus to js */ .container{width:150px;height:100px;position:relative;margin:25px auto;perspective:600px}#box{width:100%;height:100%;position:absolute;transform-style:preserve-3d;transition:transform 1s}#box figure{display:block;position:absolute;border:1px solid #000;line-height:98px;font-size:45px;text-align:center;font-weight:700;color:#fff}figure{margin:0}#box .back,#box .front{width:148px;height:98px}#box .left,#box .right{width:48px;height:98px;left:50px}#box .bottom,#box .top{width:148px;height:48px;top:25px;line-height:48px}#box .front{background:hsla(000,100%,50%,.7)}#box .back{background:hsla(160,100%,50%,.7)}#box .right{background:hsla(120,100%,50%,.7)}#box .left{background:hsla(180,100%,50%,.7)}#box .top{background:hsla(240,100%,50%,.7)}#box .bottom{background:hsla(300,100%,50%,.7)}#box .front{transform:translateZ(25px)}#box .back{transform:rotateX(180deg) translateZ(25px)}#box .right{transform:rotateY(90deg) translateZ(75px)}#box .left{transform:rotateY(-90deg) translateZ(75px)}#box .top{transform:rotateX(90deg) translateZ(50px)}#box .bottom{transform:rotateX(-90deg) translateZ(50px)}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="container">
<div id="box" class="show-front"><figure class="front">1</figure><figure class="back">2</figure><figure class="right">3</figure><figure class="left">4</figure><figure class="top">5</figure><figure class="bottom">6</figure></div>
</section>
<section id="options"><p><button id="left">Left</button><button id="right">Right</button></p></section>

How do I scale down a div within another div using CSS?

I'm trying to scale down a div (divB) that's inside of another div (divA). The problem is that divA's height is specified by its contents. When divB gets scaled down, the height of divA doesn't change... This happens when a transform is applied because it doesn't actually change the pixel count, it changes the size of the pixels themselves (at least I'm pretty sure that's what's happening). So the easy fix is to manually set the height of divA to be the size of divB multiplied by the scale factor.
However, if I do this, I need to reset the height manually every time the contents of divA change. In my case, this is very cumbersome as there will be a ton of changes to divA's contents. So, I'm wondering if there is a simpler way to do this, preferably using CSS.
Here is a simple JSFiddle to demonstrate the problem: http://jsfiddle.net/turtlewaxer1100/82cux/8/
Just add some elements, scale down, and you'll see what I mean about the height not adjusting. If you click "Fix Height" then it'll adjust the height properly, but then if you add more elements the height doesn't adjust unless you fix it again...
html
<div>
<div class="wrapper">
<div id="scalar"></div>
</div>
<div class="buttons">
<div id="button">
<input type="button" value="Add" />
</div>
<div id="scaleDown">
<input type="button" value="Scale Down" />
</div>
<div id="scaleUp">
<input type="button" value="Scale Up" />
</div>
<div id="fixHeight">
<input type="button" value="Fix Height" />
</div>
</div>
</div>
css
.wrapper {
float:right;
width: 200px;
background-color: black;
}
.section {
margin-left:75px;
height: 50px;
width: 50px;
background-color: red;
}
.buttons {
float:left;
}
.scaleDown {
-webkit-transform: scale(0.75);
-moz-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 0 0;
-moz-transform-origin: 50% 0 0;
-ms-transform-origin: 50% 0 0;
transform-origin: 50% 0 0;
}
jquery
var section = $("<div class='section'></div>")
$(document).ready(function () {
$("#button").children().click(function () {
$("#scalar").append(section.clone(false));
});
$("#scaleDown").children().click(function () {
$("#scalar").addClass("scaleDown");
});
$("#scaleUp").children().click(function () {
$("#scalar").removeClass("scaleDown");
});
$("#fixHeight").children().click(function () {
$(".wrapper").height($("#scalar").height()*.75)
});
});
So I couldn't find an answer using just CSS, but I did find a fairly simple javascript solution. There is a DOM event called "DOMSubtreeModified", which will fire every time any element within the current element's hierarchy is changed. So, what I did is test whether the height of the element is different from the previous height each time this event is fired. If it is different, then set it accordingly. This way you can catch all dynamic height changes without any regard to what specifically changed the height.
Here is a fiddle to demonstrate the idea: http://jsfiddle.net/turtlewaxer1100/E6D2H/5/
HTML
<div class="wrapper">
<div id="scalar"></div>
</div>
<div class="buttons">
<div id="button">
<input type="button" value="Add" />
</div>
</div>
CSS
.wrapper {
float:right;
width: 200px;
background-color: black;
}
.section {
margin-left:75px;
height: 50px;
width: 50px;
background-color: red;
}
.buttons {
float:left;
}
#scalar
{
-webkit-transform: scale(0.75);
-moz-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 0 0;
-moz-transform-origin: 50% 0 0;
-ms-transform-origin: 50% 0 0;
transform-origin: 50% 0 0;
}
JS
var section = $("<div class='section'></div>")
var scaleFactor = 0.75;
var originalHeight = 0;
$(document).ready(function () {
$("#button").children().click(function () {
$("#scalar").append(section.clone(false));
});
$(".wrapper").bind('DOMSubtreeModified', function() {
var height, heightOffset;
height = $("#scalar").height();
if (height && originalHeight !== height) {
heightOffset = height * scaleFactor;
$(this).height(heightOffset);
return originalHeight = height;
}
});
});

-webkit-transform: translate(417px, 0px)

I have a problem with the web kit translate property.
When I reduce the browser window an iPhone screen appears.
In the iPhone screen I have implemented a slider functionality.
You can see two arrows; left and right arrow.
When you click the right arrow it moves to the next slider,
but in the right slider the images do not show up. It is due to the web kit property.
I don't know how to fix it.
I am providing two li tags; how to fix it?
http://jsfiddle.net/UL3R2/
<li style="display: table-cell; width: 417px; vertical-align: top; left: 0px; -webkit-transition: 0ms; -webkit-transform: translate(0px, 0px) translateZ(0px);"
data-index="0">
<li style="display: table-cell; width: 417px; vertical-align: top; border: 1px solid red; left: -417px; -webkit-transition: 0ms; -webkit-transform: translate(417px, 0px) translateZ(0px);"
data-index="1">
Here I am giving exaple of one function, as you hve written many function with same functinality,
Instead of append span, keep the span there with style="display:none"
with the script make it visible by changing the class or attribute.
$('document').ready(function() {
window.setTimeout(function() {
$('.cubeCellSecurity').each(function() {
var htmlText = $(this).attr('data-text');
$(this).append('<div class="cubeTextStyleSecurity">' + htmlText + '<span class='divStockSecurity' style="display:none">Security</span></div>');
$(this).hover(
function() {
$(".cubeTextStyleSecurity").style().attr("display","");
});
//function(){ $(this).style().attr("display","none"); });
});
}, 600);
});
An if you want me to update fiddle, Please remove the unnecessary html part from fiddle

Categories

Resources