I`m trying to create a page, splitted horizontally or vertically. I want a nice transition between pages, splitted differently.
My solution is a background element with transform:rotateZ(0 or 90deg) and flex container with two elements:
<template>
<div id="app">
<div id="split-page-bg" :class="['split-' + splitType]"></div>
<div id="split-page" :style="{ 'flex-direction': flexDirection }">
<div id="split-page-part-first">
<p>Content #1</p>
<button #click="switchSplitType">{{ buttonText }}</button>
</div>
<div id="split-page-part-second">
<p>Content #2</p>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
buttonText() {
return (
"Switch to " +
(this.splitType === "horizontal" ? "vertical" : "horizontal")
);
},
flexDirection() {
return this.splitType === "horizontal" ? "column" : "row";
}
},
data() {
return {
splitType: "horizontal"
};
},
methods: {
switchSplitType() {
this.splitType =
this.splitType === "horizontal" ? "vertical" : "horizontal";
}
}
};
</script>
<style>
#app {
font-family: sans-serif;
font-size: 3rem;
height: 100vh;
width: 100vw;
padding: 0;
margin: 0;
}
#split-page-bg {
--w: max(200vw, 200vh);
--offset-percentage-vertical: 50vh;
--offset-percentage-horizontal: 50vw;
top: calc(-0.5 * var(--w) + 100vh - var(--offset-percentage-vertical));
left: calc(-0.5 * var(--w) + 100vw - var(--offset-percentage-horizontal));
width: var(--w);
height: var(--w);
position: fixed;
z-index: -10;
transition: transform 0.3s ease;
background: linear-gradient(0deg, #ff7d00 50%, #15616d 0%);
}
.split-horizontal {
--offset-percentage-vertical: 50vh;
}
.split-vertical {
transform: rotateZ(90deg);
}
#split-page {
width: 100vw;
height: 100vh;
display: flex;
}
#split-page-part-first {
flex: 0 1 50%;
}
#split-page-part-second {
flex: 0 1 50%;
}
button {
font: inherit;
}
</style>
Codepen
But it`s hard to work with separate background element. Sometimes background does not match with containers, there is a 1-2 px difference in width/height.
Question is, is there a better way for implementing this? Can I somehow animate containers like this and work with them in developer-friendly way?
Related
I'm new to VueJS. I spent the last two hours trying to animate a slider, and finally I found a solution, but I don't understand why I need to set my image in position absolute to have the slide effect using the transform: translate property. Can someone explain me why?
Here's the working code:
SCSS:
#slider {
margin-top: 20px;
width: 450px;
height: 187.5px;
position: relative;
overflow: hidden;
.layover {
#extend %layover;
}
.wrapper-image-slider {
width: 450px;
height: 187.5px;
position: relative;
overflow: hidden;
.slide-image {
width: 100%;
position: absolute; /* important */
top: 0;
left: 0;
bottom: 0;
right:0;
}
.thumb-text {
position: absolute;
top: 0;
left: 0;
}
}
.left-slide-enter-active, .left-slide-leave-active {
transition: 1s;
}
.left-slide-enter {
transform: translate(100%, 0);
}
.left-slide-leave-to {
transform: translate(-100%, 0);
}
}
HTML:
<div id="slider">
<div class="layover"></div>
<transition-group name="left-slide" tag="div" class="wrapper-image-slider">
<div v-for="(post, index) in slider" :key="post.id" v-if="(activeImageSlider == index)">
<img class="slide-image" :src="post.img">
<div class="thumb-text">
<div class="label">
{{ slider[activeImageSlider].label }}
</div>
<h2>
{{ slider[activeImageSlider].title }}
</h2>
<div class="descr">
{{slider[activeImageSlider].descr }}
</div>
</div>
</div>
</transition-group>
</div>
I don't think you need to use a list transition for this. If you create a computed property that just returns the active slide then you can use a normal transition with the mode 'out-in'.
https://jsfiddle.net/9oj1h8r3/1/
new Vue({
el: "#app",
data: {
activeSlideIndex: 0,
slides: [
{ img: "https://picsum.photos/200/300?random=1" },
{ img: "https://picsum.photos/200/300?random=2" },
{ img: "https://picsum.photos/200/300?random=3" },
{ img: "https://picsum.photos/200/300?random=4" }
]
},
computed: {
activeSlide() {
return this.slides[this.activeSlideIndex]
}
}
})
I'm using javascript to display dynamic text and image, but having trouble with formatting.
I'm using display: flex to put text and image next to each other, but am having trouble horizontally aligning them. Right now, it looks like:
But I'd like to horizontally align them so that it becomes:
I've tried the following, but this didn't work
#conA #container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
Update:
Implementing the following code but NOT setting the height of #heroText and #images the same
#conA #container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
does center text and images horizontally when both are present. However, once the images disappear, the text jumps. Here's how it's behaving https://imgur.com/a/7yYl8zO I'd like the text to not move when images disappear
Once I set the heights of #heroText and #images the same, it then turns to this:
I'm also looking to center the whole text+image in the parent div (#conA, which takes up full screen 100vh). I tried the following:
#conA {
position: relative;
}
#conA #container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
But it looks odd b/c text keeps moving/jumping depending on whether there's an image displayed.
So when there's no image, it looks like:
When when images appear, text moves to left so that the whole thing becomes centered:
How can I fix the position of text here? I'd like the whole thing is centered when there's an image. So when there's no image:
When there's an image:
html
<section id="conA">
<div id="container">
<div id="heroText">
<div id="text-fixed">I'm a fixed text</div>
<div id="text"></div>
</div>
<div id="images"></div>
</div>
</section>
css
#conA {
height: 100vh;
position: relative;
}
#conA #container {
margin: 0;
}
#conA #text {
display: initial;
border-right: 3px solid #56525E;
}
#heroText {
line-height: 1.7;
font-size: 30px;
width: 800px;
}
#conA #container {
margin: 0;
display: flex;
}
#heroText {
height: 400px;
}
#conA #images {
height: 400px;
}
#conA img
{
display:none;
height: 400px;
}
#conA img.invisible
{
visibility: hidden;
}
#conA img.show
{
display:inline;
}
#conA img.anim1
{
animation-duration: 2000ms;
}
#conA img.anim2
{
animation-duration: 2000ms;
}
#conA img.anim3
{
animation-duration: 2000ms;
}
.fadeIn
{
animation-name: fadeIn;
}
#keyframes fadeIn
{
0% {opacity: 0;}
100% {opacity: 1;}
}
.fadeOut
{
animation-name: fadeOut;
}
javascript code for dynamic display of text and image
// List of sentences
var _CONTENT = [ "I'm the first sentence.", "I'm the second sentence."
, "I'm the third sentence.", "I'm the fourth sentence." ];
var IMAGE_URLS = ['img/image1.png', 'img/image2.jpg', 'img/image3.png', 'img/image1.png','img/image2.png','img/image3.png', 'img/image4.png','img/image5.png'];
var IMAGES = jQuery.map(IMAGE_URLS, function (url, index){
var img = document.createElement('img');
img.setAttribute('src', url);
img.classList.add('anim'+((index%2)+1));
img.classList.add('fadeOut');
document.getElementById('images').appendChild(img);
return img;
});
// Current sentence being processed
var _PART = 0;
// Character number of the current sentence being processed
var _PART_INDEX = 0;
// Holds the handle returned from setInterval
var _INTERVAL_VAL;
// Element that holds the text
var _ELEMENT = document.querySelector("#text");
// Implements typing effect
function Type() {
var text = _CONTENT[_PART].substring(0, _PART_INDEX + 1);
_ELEMENT.innerHTML = text;
_PART_INDEX++;
// If full sentence has been displayed then start to delete the sentence after some time
if(text === _CONTENT[_PART]) {
var imgIndexBase = _PART*2;
IMAGES[imgIndexBase].classList.remove('fadeOut');
IMAGES[imgIndexBase+1].classList.remove('fadeOut');
setTimeout(function () { IMAGES[imgIndexBase].classList.add('fadeIn'); }, 0);
setTimeout(function () { IMAGES[imgIndexBase].classList.add('show'); }, 0);
setTimeout(function () { IMAGES[imgIndexBase].classList.add('fadeOut'); }, 2000);
setTimeout(function () { IMAGES[imgIndexBase].classList.remove('fadeOut'); }, 3000);
setTimeout(function () { IMAGES[imgIndexBase].classList.remove('show'); }, 3000);
setTimeout(function () { IMAGES[imgIndexBase + 1].classList.add('fadeIn'); }, 0);
setTimeout(function () { IMAGES[imgIndexBase + 1].classList.add('show'); }, 0);
setTimeout(function () { IMAGES[imgIndexBase + 1].classList.add('fadeOut'); }, 2000);
setTimeout(function () { IMAGES[imgIndexBase + 1].classList.remove('fadeOut'); }, 3000);
setTimeout(function () { IMAGES[imgIndexBase + 1].classList.remove('show'); }, 3000);
clearInterval(_INTERVAL_VAL);
setTimeout(function() {
_INTERVAL_VAL = setInterval(Delete, 50);
}, 4000);
}
}
// Implements deleting effect
function Delete() {
var text = _CONTENT[_PART].substring(0, _PART_INDEX - 1);
_ELEMENT.innerHTML = text;
_PART_INDEX--;
// If sentence has been deleted then start to display the next sentence
if(text === '') {
clearInterval(_INTERVAL_VAL);
// If last sentence then display the first one, else move to the next
if(_PART == (_CONTENT.length - 1))
_PART = 0;
else
_PART++;
_PART_INDEX = 0;
// Start to display the next sentence after some time
setTimeout(function() {
_INTERVAL_VAL = setInterval(Type, 100);
}, 500);
}
}
// Start the typing effect on load
_INTERVAL_VAL = setInterval(Type, 100);
See example below using flex CSS. First section with image, second section with no images in .images div.
There is a lot to explain with flex, but it is super powerful when it comes to dynamic layout. Hope this gets you on the right track.
Also you need to relax on your id attribute usage, valid html only allow single usage of an id attribute value. Use class attribute for multiple instances, and id for single usage instances.
id attribute value should only ever be used once, never multiple times.
BODY {
padding: 1rem;
margin: 0;
}
SECTION .container {
border: 1px black solid;
padding: 2rem;
display: flex;
align-items: center;
flex-wrap: wrap;
flex-direction: initial;
min-height: 100px;
justify-content: center;
margin-bottom: 1rem;
}
SECTION .container .hero-text {
width: 50%;
background: cyan;
}
SECTION .container .images {
width: 50%;
background: red;
}
SECTION .container .images IMG {
display: block;
width: 100%;
}
<section>
<div class="container">
<div class="hero-text">
<div class="text-fixed">I'm a fixed text</div>
<div class="text">And I am loving life :)</div>
</div>
<div class="images">
<img src="https://i.imgur.com/q5Y5RCH.png" alt="" />
</div>
</div>
</section>
<section>
<div class="container">
<div class="hero-text">
<div class="text-fixed">I'm a fixed text</div>
<div class="text">And I am loving life :)</div>
</div>
<div class="images">
<!-- no images -->
</div>
</div>
</section>
I've got transition(expand/NotExpand) on the grid cells of my CSS grid which is triggered onClick.
PROBLEM:
The problem is on the initial click, the expansion/transition won't play and the element would simply snap in to place. Of course this also has a reverse transition but since it's the second click of that child cell/element, the transition would play.
~Parent grid~
<template>
<div class="jobGrid myr" id="jobGrid">
<job-cell v-for="job in jobs" :key="job.id" :job="job" />
</div>
</template>
<style scoped>
.jobGrid {
display: grid;
grid-template-columns: 10% 10% 10% 10% 10% 10% 10% ;
grid-template-rows: 25% 25% 25% 25% ;
grid-column-gap: 5%;
grid-row-gap: 3%;
margin: auto;
margin-top: 2%;
width: 90%;
height: 90%;
max-width: 90%;
max-height: 90%;
overflow-x: auto;
}
</style>
~Child cell~
<template>
<div class="scene" #click="toggleJobData($event)" :style="expandCSS" >
<div>
{{job.title}}
</div>
</div>
</template>
<style>
.scene {
background: peru;
transition: left 3s, top 3s, width 3s, height 3s;
}
</style>
<script>
export default {
props: ['job'],
data() {
return {
expand: false,
expandCSS: {zIndex: 1},
}
},
computed: {
gridCSS() {
return this.$store.state.job.jobSelectorCSS
}
},
methods: {
toggleJobData(e) {
let currentPos = e.target.getBoundingClientRect()
let expand = {
position: 'relative',
left: `${this.gridCSS.left - currentPos.left}px`,
top: `${this.gridCSS.top - currentPos.top}px`,
width: `1041%`,
height: `400%`,
//zIndex: 2,
}
let notExpand = {
position: 'relative',
left: '0px',
top: '0px',
width: '100%',
height: '100%',
//zIndex: 1,
}
if(!this.expand) {
this.expand = !this.expand
this.expandCSS = expand
}else{
this.expand = !this.expand
this.expandCSS = notExpand
}
}
}
}
</script>
*** This is what you will get onLoad, or the NotExpand state.
Here, I clicked "Job2" and it will expand beyond its cell to fill up the entire grid.
I assume that the transition is applied immediately after onLoad
I think the "missing transition" is lack of CSS properties for its initial state. Until the first click, there are no properties to transite from.
Try styling your child cell with the initial props, that would be transited, like this:
.scene {
background: peru;
transition: all 3s;
position: relative;
left: 0px; top: 0px;
width: 100%; height: 100%;
}
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>
);
};
This question already has answers here:
White space around css3 scale
(12 answers)
Closed 7 years ago.
I have a little problem with scale transformation. I wish resize element, but when I do, my old size occupies the space, and the next element undergoes this old size. How to remove this constraints ?
HTML
<!-- White space with Scale -->
<div class="scale"></div>
<div class="scale"></div>
<!-- Whitout Scale -->
<div></div>
<div></div>
CSS
div:nth-of-type(even) { background: blue; }
div:nth-of-type(odd) { background: red; }
div {
width: 100px;
height: 100px;
}
.scale {
transform: scale(0.5);
transform-origin: top left;
}
JSFiddle: https://jsfiddle.net/c7d2s21y/
Thank you for your response.
var scaleTo = 0.5,
itemWidth = $('.scaleB').width(),
itemHeight = $('.scaleB').height()
;
function scaleThis(meausure) {
var output = meausure * scaleTo;
return output;
}
$('.scaleB').on({
'mouseover': function(event) {
$(this).css({
'width' : scaleThis(itemWidth) + 'px',
'height' : scaleThis(itemHeight) + 'px'
});
},
'mouseout': function(event) {
$(this).css({
'width' : itemWidth + 'px',
'height' : itemHeight + 'px'
});
}
});
.wrapper {
background-color: #cccccc;
}
.wrapper:after {
content: "normal";
}
.wrapperScale {
background-color: #dddddd;
}
.wrapperScale:after {
content: "wrapped";
}
.wrapper_jQuery:after {
content: "jQuery";
}
.wrapper div:nth-of-type(even),
.wrapperScale div:nth-of-type(even) {
background: blue;
}
.wrapper div:nth-of-type(odd),
.wrapperScale div:nth-of-type(odd) {
background: red;
}
.scale, .wrapperScale div, .scaleB {
width: 50px;
height: 50px;
}
.scale:hover, .wrapperScale:hover {
transform: scale(0.5);
transform-origin: top left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="wrapper">
<!-- White space with Scale -->
<div class="scale"></div>
<div class="scale"></div>
</div>
<!-- Whitout Scale -->
<div class="wrapper wrapperScale">
<div></div>
<div></div>
</div>
<!-- jQuery -->
<div class="wrapper wrapper_jQuery">
<div class="scaleB"></div>
<div class="scaleB"></div>
</div>
That's that CSS transformations actually do, it doesn't affect the surrounding elements, you can try to wrap the DIVs inside another element and apply the scaling to that element, but it will not affect other elements outside, just the contents, other than that, you will have to manipulate the actual sizes from your DIVs via java Script or a js library such as jQuery.
You try this code i hope work for you :
<style>
div:nth-of-type(even) { background: blue; }
div:nth-of-type(odd) { background: red; }
div {
width: 100px;
height: 100px;
}
.scale {
transform: scale(1);
transform-origin: top left;
}
</style>