Initial CSS transition won't play - html

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%;
}

Related

How to create a splitted page with transitions

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?

Is there any way in CSS to make other objects move when another object is scaled?

I have a row of elements, and they have a hover animation to make them scale up. Is it possible to make other images next to them change position on scale to prevent the overlap?
body {
background-color:#1a1a1a;
}
img{
max-width: 15%;
transition-duration: 0.5s;
transform-origin: center;
border-radius: 25px;
overflow: hidden;
margin: 50px;
}
img:hover{
cursor: pointer;
transform: scale(110%);
}
<img src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg">
<img src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg">
<img src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg">
and example of the effect I am looking for would be something that looks like this:
This will scale images up and down, dependent on classes. I've amended your css slightly for display purposes and add the JS code (left comments as clear as possible).
// define function to return all siblings of hovered element
function getAllSiblings(element, parent) {
const children = [...parent.children];
return children.filter((child) => child !== element);
}
// grab all img elements
const imgs = document.querySelectorAll('img');
imgs.forEach((i) => {
// define siblings using function above
const siblings = getAllSiblings(i, document.getElementById('parent'));
// *hover in*
i.addEventListener('mouseover', function () {
// add an active class when hovered (amended your css)
this.classList.add('active');
// add small class to all sibling elements
siblings.forEach((sibling) => {
sibling.classList.add('small');
});
});
// *hover out*
i.addEventListener('mouseleave', function () {
// remove active class and small classes so hovered element reverts to normal
this.classList.remove('active');
this.classList.contains('small') && this.classList.remove('small');
// remove class small on all siblings so that everything is reverted to initial display
siblings.forEach((sibling) => {
sibling.classList.remove('small');
});
});
});
body {
background-color: #1a1a1a;
/* added for display purposes */
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* added for getting parent element */
#parent {
margin: auto;
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
width: 100%;
}
img {
max-width: 15%;
transition-duration: 0.5s;
transform-origin: top;
transform-origin: left;
border-radius: 25px;
}
/* added for changing hover states */
img.active {
max-width: 17%;
cursor: pointer;
transform: scale(120%);
transform-origin: center;
}
img.small {
max-width: 17%;
transform: scale(80%);
transform-origin: center;
}
<div id="parent">
<img
src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg"
/>
<img
src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg"
/>
<img
src="https://www.tazzadesign.com/wp-content/uploads/sites/65/2013/11/dummy-image-square.jpg"
/>
</div>
Check this out (after hover, the shape is changing width & height):
const divs = document.querySelectorAll('div');
const reset = () => {
divs.forEach(div => div.classList.remove('active'))
}
divs.forEach(div => div.addEventListener('mouseover', () => {
reset();
div.classList.toggle('active');
}));
divs.forEach(div => div.addEventListener('mouseout', () => {
reset();
}))
section {
height: 150px;
width: 600px;
display: flex;
justify-content: space-between;
align-items: center;
border: 2px solid red;
}
div {
flex: 1;
height: 100px;
border: 2px solid black;
transition: flex .2s, transform .2s;
margin: 1em;
}
div.active {
transform: scaleY(1.5);
flex: 2;
}
<section>
<div></div>
<div></div>
<div></div>
<div></div>
</section>

Why I need to set the image in position absolute when doing a slide-effect in VueJS?

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]
}
}
})

Containers are not inline in my Vue Swiper

I have written simple swiper on in my vue app but I have a problem with containers which are not inline. The second is under the first one.
The second problem is that slider element should be visible only in viewport of swiper but it is not (viewport is border is blue).
I want to achieve a pretty effect of fluent slide show.
You can see it in my example:
https://jsfiddle.net/eywraw8t/547878/
How can I fix it?
<template>
<div class="swiper">
<transition-group
tag="div"
class="slides-group"
:name="transitionName"
>
<div :key="currentIndex" class="slide">
<slot v-bind:element="current" />
</div>
</transition-group>
<div class="pagination">
<button #click="next">next</button>
</div>
</div>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: []
}
},
data() {
return {
currentIndex: 0,
transitionName: 'slide-next'
}
},
computed: {
current() {
return this.data[this.currentIndex];
}
},
methods: {
next() {
this.currentIndex++;
}
}
}
</script>
<style lang="scss" scoped>
.swiper {
width: 100%;
position: relative;
}
.slide-next-enter-active,
.slide-next-leave-active {
transition: transform 0.5s ease-in-out;
}
.slide-next-enter {
transform: translate(100%);
}
.slide-next-leave-to {
transform: translate(-100%);
}
.slide-prev-enter-active,
.slide-prev-leave-active {
transition: transform 0.5s ease-in-out;
}
.slide-prev-enter {
transform: translate(-100%);
}
.slide-prev-leave-to {
transform: translate(100%);
}
</style>
Try the following CSS changes
1)
to .swiper, add:
overflow:hidden;
2)
to .slide, add:
display: inline-block;
Then, change your transition settings for aesthetics.
.swiper {
margin-left: 100px;
position: relative;
border: 1px solid blue;
overflow:hidden;
}
.slide {
border: 1px solid red;
width: 200px;
height: 200px;
display:inline-block;
}
Please note, using an inline display will cause it to break into multi-lines if the line width exceeds the containers width.
You may want to set absolute displays on them with fixed positions, and manipulate those positions to achieve the desired affect.

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>
);
};