Slide a div from the bottom underneath another div with angular animations - html

As you can see in the screenshot below, I hava a tab on the bottom of my page. When I click on it, I want it to slide underneath the <div> containing "Test" using angular animations. The problem is, that the pagesize should be responsive and therefore I cannot use px-values. I tried percentage as well, but that value refers to my tab-div, not the overall height.
Screenshot
My component:
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss'],
animations: [
trigger('tabState', [state('default', style({
transform: 'translateY(0)'
})
),
state('open', style({
transform: 'translateY(-100%)'
})),
transition('default <=> open', animate(500))
])
]})
export class TestComponent {
state = 'default';
onComeIn() {
this.state === 'default' ? this.state = 'open' : this.state = 'default';
}
}
My HTML:
<div class="mainContainer">
<mat-toolbar color="primary">
<div class="d-flex align-items-center justify-content-between">
<span>Test</span>
</div>
</mat-toolbar>
<div class="mainContentContainer">
<div class="d-flex flex-column" style="height: 100%">
<div>content</div>
<div class="mt-auto">
<div class="tabContainer" [#tabState]="state">
<div class="tab" (click)="onComeIn()">Tab</div>
</div>
</div>
</div>
And finally the css:
.tab {
box-sizing: border-box;
height: 4.2em;
width: 33%;
background-color: white;
padding: 1em 1.2em 0.45em 1.2em;
border-radius: 0.5em 0.5em 0 0;
box-shadow: 0 0.05em #b7b7b7;
}
.mainContainer {
width: 100%;
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.mainContentContainer {
flex: 1;
background-color: #455864;
}

The issue is more about css :
I changed the initial value of the tabContainer class to this :
.tabContainer {
position: fixed;
bottom: 0;
width: 100%;
}
Then in the animation definition, removed the bottom and added the top one :
state('open', style({
bottom: 'initial',
top: '20px'
})),
Here is the running example in editor.

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?

How can I disable my submit button when a text area is left blank?

I am trying to make a submit button that is disabled when a text area is left blank, but alas I am not seeing results. If any advice could be spared it would be greatly appreciated. Here is the code that pertains to this problem. This submit button works as if it were not disabled, so I'm guessing there must be something wrong in my typescript or the way that I am constructing the disable in the first place, but I am getting to the point where I am running out of Ideas. If anyone has any questions feel free to ask.
<template>
<div id="editEntryDiv">
<div id="mainContent" v-if="loaded">
<PartsForm v-model="localPartEntry" />
</div>
<Teleport to="#mainContent">
<div class="actionBar">
<!-- Empty Div Required for formatting -->
<div>
<button id="deleteButton" #click="deleteItem(parseInt(id))">
<i class="fas fa-trash"></i>
<span>Delete</span>
</button>
</div>
<!-- Empty Div Required for formatting -->
<div>
<button
id="submitButton"
:class="{ disabled: localPartEntry.partNumber == undefined }"
:disabled="localPartEntry.partNumber == undefined"
#click="submitItem"
>
<i class="fas fa-paper-plane"></i>
<span>Submit</span>
</button>
</div>
</div>
</Teleport>
</div>
</template>
here is the typescript:
<script lang="ts">
import { defineComponent } from "vue";
import { usePartStore } from "../stores/part-store";
import PartsForm from "../components/PartsForm.vue";
import { PartDefinition } from "../types/PartDefinition";
import { mapStores } from 'pinia'
export default defineComponent({
components: {
PartsForm,
},
data() {
return {
loaded: false,
localPartEntry: {} as PartDefinition,
};
},
watch: {
localPartEntry: {
handler() {
if (!this.loaded) return
sessionStorage.setItem("unsavedPart", JSON.stringify(this.localPartEntry))
},
deep: true
}
},
computed: {
...mapStores(usePartStore),
id(): string {
return this.$route.params.id.toString();
},
},
methods: {
async submitItem(): Promise<void> {
this.localPartEntry.id = parseInt(this.id);
if (await this.partStore.editPartDefinition(this.localPartEntry))
if (await this.partStore.getParts())
this.$router.push({
path: `/`,
});
},
async deleteItem(id: number) {
if (confirm("Are you sure you want to delete this entry?"))
if (await this.partStore.deletePartDefinition(id))
if (await this.partStore.getParts())
this.$router.push({
path: `/`,
});
},
},
mounted() {
for (let element of this.partStore.partEntries as PartDefinition[]) {
if (element.id == parseInt(this.$route.params.id.toString())) {
this.localPartEntry.id = element.id;
this.localPartEntry.partNumber = element.partNumber;
this.localPartEntry.variant = element.variant;
this.localPartEntry.revision = element.revision;
this.localPartEntry.description = element.description;
this.localPartEntry.supplier = element.supplier;
this.localPartEntry.previewImagePath = element.previewImagePath;
this.localPartEntry.previewImageDateTime = element.previewImageDateTime;
this.localPartEntry.obsolete = element.obsolete;
this.localPartEntry.internalOnly = element.internalOnly;
this.loaded = true;
break;
}
}
},
});
</script>
And finally, the css:
<style lang="sass" scoped>
#editEntryDiv
width: 100%
height: 100%
background: $primary-background
display: flex
flex-direction: column
overflow-y: auto
-ms-overflow-style: none // for Internet Explorer, Edge */
scrollbar-width: none // for Firefox */
&::-webkit-scrollbar
display: none // for Chrome, Safari, and Opera */
#mainContent
margin-top: 1rem
margin-bottom: 5rem
flex-grow: 1
#submitButton
border: 1px solid $primary-accent-color
font-size: 1.5rem
border-radius: .25rem
cursor: pointer
padding: .25rem .75rem
transition: background .3s, color .3s
color: $primary-accent-color
background: transparent
display: flex
flex-direction: row
justify-content: center
align-items: center
gap: .5rem
&:hover
color: $tertiary-background
background: $primary-accent-color
.disabled
background: grey !important
#deleteButton
border: 1px solid $primary-accent-color
font-size: 1.5rem
border-radius: .25rem
cursor: pointer
padding: .25rem .75rem
transition: background .3s, color .3s
color: $primary-accent-color
background: transparent
display: flex
flex-direction: row
justify-content: center
align-items: center
gap: .5rem
&:hover
color: $tertiary-background
background: $primary-accent-color
.actionBar
width: 100%
min-height: 4rem !important
background: $secondary-background
display: flex
justify-content: space-between
position: fixed
bottom: 0px
right: 0px
&>div
display: flex
flex-direction: row
align-items: center
gap: 1rem
margin: 0 1rem
</style>
You can just check with Logical NOT (!) operator which takes truth to falsity and vice versa. Hence, If there will be no value in the textarea it will return false else true.
Working Demo :
new Vue({
el: '#app',
data: {
content: ''
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Textarea content: {{ content }}
<br><br>
<textarea v-model="content"></textarea>
<button id="submitButton" :disabled="!content.trim()">
<span>Submit</span>
</button>
</div>
I just added an example to show you how to achieve. You can made the changes in your original code.

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

How do I make a content div scrollable? React and CSS

Learning React.js framework and need some pointers on styling. CSS isn't my forte.
How do I style the static content div in the middle and make it scrollable only within the div?
No styling:
https://i.imgur.com/26wNAfH.jpg
How to style this?
https://i.imgur.com/c5nYCOz.jpg
Here's the scroll function:
https://storage.googleapis.com/hatchways-app.appspot.com/assessments/data/frontend/part%202.mp4
app.css
.name {
font-weight: bold;
font-size: 20px;
}
.centered {
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
}
.center {
position: fixed;
width: 500px;
height: 200px;
top: 50%;
left: 50%;
margin-top: -100px; /* Negative half of height. */
margin-left: -250px; /* Negative half of width. */
}
.content {
text-align: center;
border: 2px solid grey;
border-radius: 5px;
position: fixed;
/* center the div */
right: 0;
left: 0;
margin-right: auto;
margin-left: auto;
/* give it dimensions */
min-height: 10em;
width: 90%;
/* just for example presentation */
top: 5em;
background-color: white;
}
Output: https://i.imgur.com/Eyv6hab.png
HTML:
import React, { Component } from "react";
import "../App.css";
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
const API = "https://www.hatchways.io/api/assessment/students";
class App extends Component {
constructor(props) {
super(props);
this.state = {
students: [],
isLoading: false,
error: null
};
}
componentDidMount() {
this.setState({ isLoading: true });
fetch(API)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error("Something went wrong ...");
}
})
.then(data =>
this.setState({ students: data.students, isLoading: false })
)
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
const { students, isLoading, error } = this.state;
if (error) {
return <p>{error.message}</p>;
}
if (isLoading) {
return <p>Loading ...</p>;
}
return (
<body>
<div className="content">
<div>
{students.map(student => (
<div key={student.id}>
<p>
<img src={student.pic} />
</p>
<p className="name">
{student.firstName} {student.lastName}
</p>
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p> Skill: {student.skill}</p>
<p>Average: {student.grades}</p>
</div>
))}
</div>
</div>
{/* <div class="card mb-3">
{students.map(student => (
<div class="row no-gutters">
<div class="col-md-4">
<img src={student.pic} class="card-img" alt="..." />
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">
{student.firstName} {student.lastName}
</h5>
<p class="card-text">
<p>Email: {student.email}</p>
<p>Company: {student.company}</p>
<p> Skill: {student.skill}</p>
<p>Average: {student.grades}</p>
</p>
</div>
</div>
</div>
))}
</div> */}
</body>
);
}
}
export default App;
This might not help I am unfamiliar with that JS framework. I am only posting this because nobody has answered and maybe this can help.
<style>
scroll
{
max-height: 400px;
overflow-y: scroll;
}
</style>
<div class="scroll">

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