ngOnInit ignores CSS transition - html

I have div which I would like to have its width increase from 0 - 100 in a 3s interval using CSS transition property. When I change this property in Chrome Developer tools, it grows nicely from 0-100 along the duration of the 3 seconds. However, if I apply the style from the component's ngOnInit(), it's instant. Am I doing something wrong?
EDIT: I did solve the problem by myself, however an answer which also explains why it works would be great.
Component.ts:
#Component({
selector: 'notFound',
templateUrl: 'app/notFound.component/notFound.component.html',
directives: [ROUTER_DIRECTIVES]
})
export class NotFoundComponent {
ngOnInit() {
(<HTMLElement>document.querySelector('.determinate')).style.width = "100%";
}
}
Component.html:
<div class="wrapper">
<i class="material-icons error">error_outline</i>
<div class="not-found-text"> No such page 'round here.</div>
<a [routerLink]="['Menu']" class="waves-effect waves-light btn blue">
<i class="material-icons">home</i>
<!--home-->
</a>
<div class="progress">
<div class="determinate"></div>
</div>
</div>
<style>
.progress {
margin-top: 30px;
background: white;
}
.determinate {
width: 0%;
background: #2196F3;
transition: width 3s ease-in-out;
}
</style>

I solved it by wrapping the call in a 0ms setTimeout. What a suprise.
ngOnInit() {
setTimeout(function () {
(<HTMLElement>document.querySelector('.determinate')).style.width = "100%";
}, 0);
}

Related

Is there a way to change the appeareance of an html text when hovering on the div that contains it?

I need to color and zoom the text when the cursor "approaches" the text (so basically when the mouse enters the area of the div surrounding the text). Right now i can make it work coloring the text only when i hover directly on it. I'll paste a snippet of the code.
HTML:
<div fxLayout="row wrap" class="max container">
<div fxFlex="100%" fxLayoutAlign="center">
<!--here there is an image-->
</div>
<div fxFlex="100%" class="centered-text" fxHide fxShow.gt-lg>
<h2 [ngClass]="{'gradient' : this.gradient,'lighter':lighter, 'zoom':zoom, 'scale':1.2}" style="margin: 0;" class="font">
hoverMe
</h2>
</div>
</div>
Typescript:
import {Component, Input, OnInit} from '#angular/core';
#Component({
selector: 'iet-box-academy',
templateUrl: './box-academy.component.html',
styleUrls: ['./box-academy.component.scss']
})
export class BoxAcademyComponent implements OnInit {
#Input() scale = 1;
#Input() img = '';
#Input() title = 'TITOLO';
#Input() descr = '';
#Input() align = "centerer";
#Input() lighter = false;
#Input() zoom = true;
#Input() gradient: boolean = false;
constructor() {
}
ngOnInit(): void {
}
}
CSS:
.container {
position: relative;
text-align: center;
color: black;
}
.zoom {
transition: transform .2s; /* Animation */
margin: 0 auto;
}
.zoom:hover {
transform: scale(1.5);
color: #00D3FF;
}
https://jsfiddle.net/wdfc7g9a/14/
You can add the :hover to the parent and add a child selector:
Change:
.zoom:hover {
transform: scale(1.5);
color: #00D3FF;
}
To:
.container:hover .zoom {
transform: scale(1.5);
color: #00D3FF;
}
Demo:
.container {
border: 1px solid red;
}
.container:hover .zoom {
background: yellow;
}
<div class="container">
This is a text
<div class="zoom">highlight this text</div>
More text
</div>
I recommend you to use centered-text class instead of zoom class. Because it is easier to give a transparent padding to it so you can have the "approaching" animation played without needing to hover directly on the text.
This code will fix your problem the moment you copy paste it to your Custom CSS:
.centered-text {
transition: all 0.3s;
margin: 0 auto;
padding:13px;
}
.centered-text:hover {
transform: scale(1.4);
color: #00D3FF;
transition: all 0.3s;
}

Draw a border inside video with angular and CSS

I am writing a SPA which very simply shows video from webcam, draws a rectangle on it (so you can place a document where the edges align with the drawn box) then I press a button to capture that image.
Everything works except for drawing the box. I want it to be somewhat inside the div, so it's not around the video but actually on the video, aligning the sides but with a, let's say, 25px difference. I now just draw a border with CSS but I'm guessing there's better solutions I just can't seem to find them.
Edit: I tried with the drawImge() - function too, didn't seem to find a way to make it work. Haven't figured out why though.
app.component.ts:
import { Component, ElementRef, OnInit, Renderer2, ViewChild } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
#ViewChild('video', { static: true }) videoElement: ElementRef;
#ViewChild('canvas', { static: true }) canvas: ElementRef;
videoWidth = 0;
videoHeight = 0;
constraints = {
video: {
facingMode: "environment",
width: { ideal: 4096 },
height: { ideal: 2160 }
}
};
constructor(private renderer: Renderer2) {}
ngOnInit() {
this.startCamera();
}
startCamera() {
if (!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
navigator.mediaDevices.getUserMedia(this.constraints).then(this.attachVideo.bind(this))
.catch(this.handleError);
} else {
alert('Sorry, camera not available.');
}
}
attachVideo(stream) {
this.renderer.setProperty(this.videoElement.nativeElement, 'srcObject', stream);
this.renderer.listen(this.videoElement.nativeElement, 'play', (event) => {
this.videoHeight = this.videoElement.nativeElement.videoHeight;
this.videoWidth = this.videoElement.nativeElement.videoWidth;
});
}
capture() {
this.renderer.setProperty(this.canvas.nativeElement, 'width', this.videoWidth);
this.renderer.setProperty(this.canvas.nativeElement, 'height', this.videoHeight);
this.canvas.nativeElement.getContext('2d').drawImage(this.videoElement.nativeElement, 0, 0);
}
drawImge(videoHeight, videoWidth){
var video = document.querySelector("#webCamera");
var canvas = document.querySelector("#videoCanvas");
var ctx = this.canvas.nativeElement.getContext('2d');
ctx.rect(0,0,videoWidth,videoHeight);
ctx.lineWidth = "6";
ctx.strokeStyle = "red";
ctx.stroke();
}
handleError(error) {
console.log('Error: ', error);
}
}
app.component.html
<div class="container vh-100">
<div class="d-flex flex-column align-items-center">
<div class="p-1" >
<video #video class="vid" id="canvas1" autoplay></video>
</div>
<div class="pb-2">
<button class="btn btn-primary" (click)="capture()">Capture Image</button>
</div>
<div class="p-1">
<canvas #canvas class="vid"></canvas>
</div>
</div>
</div>
app.component.scss
#canvas1{
border-style: inset;
border-width: 300px;
border: solid 3px green;
}
Ok guys,
I found a really easy solution with CSS, it is however a bit messy as it's hardcoded. Also all the other drawings on screen should be animateable. Meaning i.e. an arrow drawn on screen moving slightly up, resetting and making the motion again untill someone uses the capture button.
This to only say I'm still looking for a solution that's not just CSS if anyone might have one.
As for the solution:
app.component.html:
<div class="container vh-100">
<div class="d-flex flex-column align-items-center">
<div class="p-1 wrapper" >
<video #video class="vid" id="canvas1" autoplay></video>
<div class="trapeziod"></div>
<div class="rect"></div>
</div>
<div class="pb-2">
<button class="btn btn-primary" (click)="capture()">Capture Image</button>
</div>
<div class="p-1">
<canvas #canvas class="vid"></canvas>
</div>
</div>
</div>
So just another div in the div where the video resides, called rect.
Then:
app.component.scss
#canvas1{
border-style: inset;
border-width: 300px;
border: solid 2px black;
}
.trapezoid {
width: 50px;
height: 0;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-bottom: 100px solid #7FFF00;
}
.wrapper{
position: relative;
}
.rect{
position: absolute;
left: calc(10% - 90px);
top: calc(20% - 100px);
border: solid 8px #7FFF00;
width: 1060px;
height: 580px;
}
This is perfectly how it should look.

how to make Angular animations div to show on mouse enter?

I'm trying to animate a div, when the mouse enters that specific div, apparently i couln't make it done.
Any ideas how this would work in my context?
This is the animations inside the TS file
animations: [
trigger('explainerAnim', [
transition('* => *', [
query('.card', style({ opacity: 0, transform: ' translateX(-400px'})),
query('.card', stagger('100ms', [
animate('1000ms 0.9s ease-out', style({opacity:1, transform: 'translateX(0)'}))
]))
])
])
]
And this is the div that i want to show based on mouse enter
<div [#explainerAnim] class="col-sm-12 text-center about-page">
<div class="card">
<div class="developer-photo"></div>
<div class="card-body">
<h2>Who`s this guy?</h2>
<p>
I'm a Full-Stack Developer working for AppyWay in London. <br />
I have a serious passion for implementing high quality web
applications <br />
using the Microsoft stack E.g .Net Core.
</p>
</div>
</div>
<div class="card developer-details">
<div class="card-header developer-header">
<h2>I`m Norbert Csibi</h2>
<h5>Full-Stack Developer</h5>
</div>
<div class="card-body">
<p>
I am passionate about building excellent software that improves
the lives of those around me.
</p>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">EMAIL</th>
<td>#gmail.com</td>
</tr>
<tr>
<th scope="row">PHONE</th>
<td>55255</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
You can add a (mouseenter) and (mouseleave) directives on the div element that you want to trigger the animation.
<div (mouseenter)="startAnimation()" (mouseleave)="stopAnimation()">
</div>
Then you can use the angular animation states to add two states, one animated and the other without it. And set the two different states inside the startanimation() and stopAnimation() methods.
Here I let you an example of how to use the angular states:
In the TypeScript:
#Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
// ...
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('closed', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green'
})),
transition('open => closed', [
animate('1s')
]),
transition('closed => open', [
animate('0.5s')
]),
]),
],
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css']
})
export class OpenCloseComponent {
isOpen = true;
toggle() {
this.isOpen = !this.isOpen;
}
}
In the HTML:
<div [#openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
Since this question was high in my search ranking, and is not that old 😉, I think others might be helped with a 'full' example
You can position the diw with the.highlight class with relative/absolute positions, but I use css-grid in this example.
TLDR;
Hostlisteners are used to change the animation states
when mouse enters and leaves
#HostListener('mouseenter', ['$event'])
onOver(event: MouseEvent): void {
this.highlightAnimationState = this.highlightStates.mouseEnter;
// console.log('mouseenter');
// console.log('Enter: this.highlightAnimationState :>> ', this.highlightAnimationState );
}
Full example >>
this is a class used to represent a row, a race track, for a team, like this:
| Team Name | (dots representing percent of max until finished at 100%) | Team Info
it is used like this in the parent component (having an array of teams to display).
race-board.component.html
<main class="main-content">
<div *ngFor="let team of raceTeams">
<app-race-track [team]="team">
<!-- if you are not familiar with ng-content (see 'app-race-track' component)
it is simply used to output whatever is inside the tags here -->
<app-race-track-divider [colorClass]="dividerPositions.getColor(i)"></app-race-track-divider>
</app-race-track>
</div>
</main>
race-track.component.html
<div
class="race-track-row"
*ngIf="team; else loadingTemplate">
<div [#highlightTrigger]="highlightAnimationState" class="highlight">
<!-- note, this is used to create higlight effect on mouseover -->
</div>
<div class="divider">
<ng-content>
<!-- here a divider line is printed; if it should be. -->
</ng-content>
</div>
<div class="team-details">
<div class="team-name">
{{team?.Name | shorten | uppercase }}
</div>
</div>
<div class="race-track" #raceTrackContainer>
<ng-container *ngFor="let item of filledDots; let i = index">
<div class="percentage-dot" id="filled-dot-{{i}}">
<mat-icon class="mat-icon-scale-up mat-icon-color-dark-grey" [inline]="true">
lens
</mat-icon>
</div>
</ng-container>
</div>
<div class="team-info">
<img src="../assets/images/race-board/raceboard-team-image.png" />
</div>
</div>
<ng-template #loadingTemplate>
... Loading race team ...
</ng-template>
</div>
You can position the div with the .highlight class using relative/absolute positions, but I use css-grid and it looks like this
race-track.component.scss
#use 'src/app/race-board-app-module' as appStyles;
/* the container of the component */
.race-track-row {
position: relative;
margin-top: 0.4rem;
width: fit-content;
display: grid;
grid-template-columns: 200px auto 80px;
grid-template-rows: auto 3rem ;
row-gap: 0.3rem;
// column-gap: 15px;
/* note: use dots to leave an area blank */
grid-template-areas:
" . track-divider . "
" team-details race-track team-info ";
}
/*note, this is used to create highlight effect on mouseover */
.highlight{
width: 100%;
grid-row: 2/3;
grid-column: 1/4;
}
.divider {
grid-area: track-divider;
}
.race-track{
grid-area: race-track;
position: relative;
padding-left: 2rem;
padding-right: appStyles.$race-track-padding-right;
display: flex;
flex-wrap: nowrap;
}
/** name (perhaps also icon and percent later?) */
.team-details {
grid-area: team-details;
justify-self: right;
align-self: center;
margin-right: 0.5rem;
padding-left: 1rem;
}
.team-name{
font-weight: 500;
}
.percentage-dot {
// align-items: center;
padding-left: appStyles.$padding-dots-container;
align-self: center;
}
.team-info{
grid-area: team-info;
display: flex;
align-items: center;
justify-content: center;
align-self: center;
}
/* variable must be imported like this for scale and calc to function */
$icon-scale: appStyles.$icon-scale;
.mat-icon-scale-up {
transform: scale($icon-scale);
padding: appStyles.$padding-dot-icons;
}
race-track.component.ts
/* you probably don't need all of the imports here, but cpy paste from implentation so .. */
import { Component, Input, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked, Renderer2, HostListener } from '#angular/core';
import { trigger, state, style, transition, animate, keyframes } from '#angular/animations';
#Component({
selector: 'app-race-track',
templateUrl: './race-track.component.html',
styleUrls: ['./race-track.component.scss'],
animations: [
trigger('highlightTrigger', [
state('in', style({
opacity: 0.1,
backgroundColor: 'lightblue'
})),
state('out', style({
opacity: 0.0,
backgroundColor: 'lightblue'
})),
/*
this transitions has keyframes to controll the in-out speed with offset (time in point from in to out, ) you can also use
*/
transition('* <=> *', animate(50, keyframes([
style({
opacity: 0.03,
offset: 0.3
}),
style({
opacity: 0.05,
offset: 0.5
}),
style({
opacity: 0.1, /* this should, but don't have to, match the 'final' state (i.e. 'in') */
offset: 1
}),
])))
])
]
})
export class RaceTrackComponent implements OnInit, OnDestroy, AfterViewChecked {
//animation:
highlightAnimationState = "";
highlightStates = {
mouseEnter: 'in',
mouseLeave: 'out'
}
#Input() public team?: RaceBoardTeam;
/** dark icons; reprecenting team score as percent of max */
public filledDots?: number[];
constructor() { }
/** Animations: hostlisteners are used to change the animation states
* when mouse enters and leaves the race-track
*/
#HostListener('mouseenter', ['$event'])
onOver(event: MouseEvent): void {
this.highlightAnimationState = this.highlightStates.mouseEnter;
// console.log('mouseenter');
// console.log('Enter: this.highlightAnimationState :>> ', this.highlightAnimationState );
}
#HostListener('mouseleave', ['$event'])
onOut(event: MouseEvent): void {
this.highlightAnimationState = this.highlightStates.mouseLeave;
// console.log('mouseleave');
// console.log('Leave: this.highlightAnimationState :>> ', this.highlightAnimationState );
}
// other stuff omitted ....
}

Vue: How to implement extended functionality of generated divs by placing them into an array?

I am using Vue to create a web app in which it is possible to create new divs (one at the time) with a button click. It is also possible to minimize these divs. It's isn't these divs that are changing size, but it needs to be this way because of functionality I cant reveal here. You can visit this jsFiddle to see it in action: jsFiddle.
If you click on the Increase button it will be possible to increase the limitation of divs that it is possible to generate on the website. If you click on the button the div will "change size".
There are some limitations though that I haven't been able to solve yet. The most important issue is that I want it to be possible to change the size of the divs, on at the time. How do I do that? It must be possible to handle several divs this way.
I have thought of placing them in an array like you can find in this: example. I think that it would also help me target some selected "small" divs for other functions with help of the index. In one case in need to select all minimized divs but the first one. Is an array the best way to get the functionality I have revealed?
Here is my progress so far... This code is not working in its present shape. The goal is to make it have the same functionality as in the codePen example, but still be able to the same things as the code in the jsFiddle.
If you prefer, visit this jsFiddle in which you can find this code:
HTML
<div id="lotsOfDivs">
<addingdivs></addingdivs>
</div>
Vue.js
var gate = 0; // This global variable is used to increase the limitation of divs to be generated.
var expArray=[]; //The array where I want to place the "small divs"
$(document).ready(function(){
$(".expandable").each(function(){ displayDiv.push($(this));});
});
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
</div>
<div id=parent v-for="n in count" :style="{ 'height': height}">
<div id="big" v-for="(item, i) in displayDiv" v-if="expand(i)>
<div class="firstChild">
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="secondChild">
<button class="done" #click="expand">EXPAND</button>
</div>
</div>
<div id="smal" v-for="(item, i) in displayDiv" v-if="expand(i)">
<button class="done" #click="expand">EXPAND</button>
</div>
</div>
</div>
`,
data: function() {
return {
displayDiv: [false],
gate: gate,
height: "",
count: 0,
i:0,
}
},
methods: {
expand: function() {
this.$set(this.displayDiv, i, !this.displayDiv[i]);
if (!this.displayDiv) {
this.height = '7vh';
}
else {
this.height = "";
}
},
createDiv: function() {
if (this.count <= gate) { // Here you can decide how many divs that will be generated
this.count++;
}
},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
CSS
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#header {
background: #30BFB7;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
height: 2vh;
margin-bottom: 2vh
}
#big {
background: #207F7A;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
height: 20vh;
margin-bottom: 2vh
}
#smal {
background: #10403D;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
height: 2vh;
padding-bottom: 2vh;
}
You need to pass $event into the function expand. And then in the function get the clicked elements parent.
Here is a working fiddle link:
https://jsfiddle.net/wwf8nwsg/15/
var gate = 0;
Vue.component('addingdivs', {
template: `
<div>
<div id="header">
<button class="addDiv" type="button" #click="createDiv">ADD LOCATION</button>
<button class="done" #click="increaseLimit">INCREASE</button>
</div>
<div class="big" v-for="n in count" :style="{ 'height': height}">
<button class="done" #click="expand($event)">Small</button>
</div>
</div>
</div>
`,
data: function() {
return {
displayDiv: [false],
gate: gate,
height: "",
count: 0,
locationsArr: ["one", "two", "three"],
}
},
methods: {
expand: function(e) {
console.log(e);
console.log(e.originalTarget.parentElement.className);
if (e.originalTarget.parentElement.className == 'big') {
e.originalTarget.parentElement.className = 'small';
e.originalTarget.innerText = 'Big';
} else {
e.originalTarget.parentElement.className = 'big'
e.originalTarget.innerText = 'Small';
}
},
createDiv: function() {
if (this.count <= gate) { // Here you can decide how many divs that will be generated
this.count++;
}
},
increaseLimit: function() {
// Here you can increase the number of divs that it's possible to generate
gate++;
}
}
});
new Vue({
el: '#lotsOfDivs',
});
Linus Borg at Vue.js forum posted a solution to this question! Click here to go to see the solution!

CSS animation without defining height, angular

I have a side pane with a pan out for each item. Similiar to this one https://angular.io/docs/js/latest/api/.
When I click on an item, I would like it to make a smooth transition animation.
My markup:
<div ng-repeat="item in mainCategories"
class="main-list"
ng-class="{'not-selected': item !== currentMainCategory}"
>
<p>{{item.title}}</p>
</div>
<div class="sub-categories">
<div ng-repeat="subCategory in item.subCategories" class="sub-categories-content" ng-click="setSubCategory(subCategory)">
<p>{{subCategory.title}}</p>
</div>
</div>
</div>
When an item is not selected, it has class not selected added to it.
This is my css:
.main-list{
//I need to define a height here
transition: 200ms cubic-bezier(0.4, 0, 1, 1);
}
.not-selected{
height:50px;
}
So the height is 50px when no item is selected. When an item is selected, the height is depending on how many items is in the current subcategory.
The problem is, when I can't define the height, the animation is not working. Is there any height property I can give to the .main-list, so the animation is working?
Here's a simple example of how to use ng-style as suggested in my comments. Run the snippet below to see it in action and view the corresponding CSS.
<div ng-repeat="item in main">
<div class="maincat" ng-click="active($index)">
<p>{{item.title}}</p>
</div>
<div class="animate" ng-style="{height: item.active ? item.sub.length*30+'px' : '0px'}">
<div ng-repeat="subitem in item.sub" class="subcat">
<p>{{subitem.title}}</p>
</div>
</div>
</div>
var app = angular.module('demo.app', ['ngAnimate']);
app.controller('MainCtrl', ['$scope', function($scope){
$scope.main = [{title: 'main1', sub: [{title: 'sub1'}, {title: 'sub2'}, {title: 'sub3'}]},{title: 'main2', sub: [{title: 'sub1'}, {title: 'sub2'}]}];
$scope.active = function(idx) {
$scope.main[idx].active = !$scope.main[idx].active;
};
}]);
#import url("//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css");
.maincat {
height: 30px;
background-color: #ccc;
width: 50%;
padding: 3px 5px;
cursor: pointer;
border-bottom: 1px solid #555;
}
.subcat {
height: 30px;
background-color: #eee;
width: 50%;
padding: 3px 5px;
}
.animate {
-webkit-transition: height linear 0.5s;
transition: height linear 0.5s;
overflow: hidden;
}
<script src="https://code.angularjs.org/1.3.15/angular.js"></script>
<script src="https://code.angularjs.org/1.3.15/angular-animate.js"></script>
<div class="container" ng-app="demo.app" ng-controller="MainCtrl">
<h1 class="h3">Expandable Menu Demo</h1>
<div ng-repeat="item in main">
<div class="maincat" ng-click="active($index)">
<p>{{item.title}}</p>
</div>
<div class="animate" ng-style="{height: item.active ? item.sub.length*30+'px' : '0px'}">
<div ng-repeat="subitem in item.sub" class="subcat">
<p>{{subitem.title}}</p>
</div>
</div>
</div>
</div>
CSS transitions require a property that the effect is going to be applied to declared. In order to specify, that the transition refers to height property you should change your code to:
transition: height 200ms cubic-bezier(0.4, 0, 1, 1);