Is it possible to use the simple menu animation from W3School in Stencil.js?
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_menu_icon_js
I'm trying to set the styling on a click event, but I can't make it work. The event is fireing but I can't set the class.
In my .tsx:
import { Component, Prop, h } from '#stencil/core';
#Component({
tag: 'topbar-component',
styleUrl: 'topbar-component.css',
shadow: true
})
export class Topbar {
private menuToggle(e) {
return (
e.classList.toggle("change");
);
}
render() {
return (
<div class="topbar-menu">
<div class="container" onClick={(e) => this.menuToggle(e)}>
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</div>
</div>
)
}
}
In my css:
.bar1, .bar2, .bar3 {
width: 35px;
height: 5px;
background-color: #333;
margin: 6px 0;
transition: 0.4s;
}
.change .bar1 {
-webkit-transform: rotate(-45deg) translate(-9px, 6px);
transform: rotate(-45deg) translate(-9px, 6px);
}
.change .bar2 {opacity: 0;}
.change .bar3 {
-webkit-transform: rotate(45deg) translate(-8px, -8px);
transform: rotate(45deg) translate(-8px, -8px);
}
I get error: Uncaught TypeError: Cannot read property 'toggle' of undefined
You could do it using a state property:
import { Component, Prop, State, h } from '#stencil/core';
#Component({
tag: 'topbar-component',
styleUrl: 'topbar-component.css',
shadow: true
})
export class Topbar {
#State() isMenuOpen: boolean = false;
private menuToggle() {
this.isMenuOpen = !this.isMenuOpen;
}
render() {
return (
<div class="topbar-menu">
<div class={{ container: true, change: this.isMenuOpen }}" onClick={(e) => this.menuToggle(e)}>
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</div>
</div>
)
}
}
I made it work. I found the classlist at last.
So just add this to your menuToggle()
private menuToggle(e) {
return (
e.currentTarget.classList.toggle("change")
);
}
I think the following code can be usefuel for handling your question:
import { Component, Prop, h } from '#stencil/core';
#Component({
tag: 'topbar-component',
styleUrl: 'topbar-component.css',
shadow: true
})
export class Topbar {
private menuToggle() {
this.isMenuOpen = !this.isMenuOpen
}
render() {
return (
<div class="topbar-menu">
{this.isMenuOpen
?
<div class=container change" onClick={() => this.menuToggle()}>
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</div>
:
<div class=container" onClick={() => this.menuToggle()}>
<div class="bar1"></div>
<div class="bar2"></div>
<div class="bar3"></div>
</div>
}
</div>
)
}
}
Related
I am trying to achieve two things:
(1) each time I click on the red arrow icon in the sidebar, I want the sidebar to collapse or open. From the below video, you'd see that the active and inactive states are already there. However, the sidebar doesn't collapse on inactive.
(2) each time I click on the Content menu, which is a drowndown menu, it doesn't open the submenu. Also, from the below video, you'd notice that the active and inactive states are already there. However, the dropdown still doesn't open on active.
Below is the video that clearly shows the error:
https://www.loom.com/share/6e0488101cee4c5b9bac7ded782b8807
Docs.js Page
import React from "react";
import { Helmet } from "react-helmet";
import SideMenu from "../docs/SideMenu";
const Docs = () => {
return (
<div className="">
<Helmet>
<title>Docs :: MyApp</title>
<meta name="description" content="MyApp" />
</Helmet>
<SideMenu />
</div >
)
};
export default Docs
SideMenu.js Component
import React, { useState } from "react";
import { Helmet } from "react-helmet";
import * as Icon from "react-bootstrap-icons";
import MenuItems from "./MenuItems";
const SideMenu = () => {
const [inActive, setInActive] = useState(false)
return (
<div className="">
<div className={`side-menu ${inActive ? "inActive" : ""}`}>
<Helmet>
<title>Docs :: MyApp</title>
<meta name="description" content="MyApp" />
</Helmet>
<div className="top-section">
<div className="logo">
<img src="/assets/media/logos/naked.png" alt="MyApp" />
</div>
<div onClick={() => setInActive(!inActive)} className="toggle-back">
{inActive ? (<Icon.ArrowLeftSquareFill />) : (<Icon.ArrowRightSquareFill />)}
</div>
</div>
<div className="search-bar">
<button className="search-bar-btn">
<Icon.Search />
</button>
<input type="text" placeholder="search" />
</div>
<div className="divider"></div>
<div className="main-menu">
<ul>
{menuItems.map((menuItem, index) => (
<MenuItems
key={index}
name={menuItem.name}
to={menuItem.to}
subMenu={menuItem.subMenu || []} />
))}
{/*<li>
<a className="menu-item">
<Icon.ArrowRightSquareFill className="menu-icon" />
<span>Dashboard</span>
</a>
</li>
<MenuItems
name={"Content"}
subMenu={[
{ name: 'Courses' },
{ name: 'Videos' },
]}
/>
<li>
<a className="menu-item">
<Icon.ArrowRightSquareFill className="menu-icon" />
<span>Support</span>
</a>
</li>*/}
</ul>
</div>
<div className="side-menu-footer">
<div className="avatar">
<img src="/assets/media/avatars/aa/brooks_lloyd.png" alt="MyApp" />
</div>
<div className="user-info">
<div className="font-size-h6">Title</div>
<div className="font-size-sm">Subtitle</div>
</div>
</div>
</div>
</div>
);
};
export default SideMenu
const menuItems = [
{ name: "Dashboard", to: "/" },
{ name: "Content", to: "/", subMenu: [{ name: "Courses" }, { name: "Videos" }], },
{ name: "Design", to: "/" },
];
MenuItems.js Component
import React, { useState } from "react";
import * as Icon from "react-bootstrap-icons";
const MenuItems = (props) => {
const { name, subMenu } = props;
const [expand, setExpand] = useState(false);
return (
<div className="">
<li>
<a onClick={() => setExpand(!expand)} className="menu-item">
<Icon.ArrowRightSquareFill className="menu-icon" />
<span>{name}</span>
</a>
{
subMenu && subMenu.length > 0 ? (
<ul className={`sub-menu ${expand ? "active" : ""}`}>
{subMenu.map((menu, index) =>
<li key={index}>
<a className="sub-menu">
<Icon.ArrowRightSquareFill className="menu-icon" />
{menu.name}
</a>
</li>
)}
</ul>) : null}
</li>
</div>
);
};
export default MenuItems
Docs.css File that contains the suspected errors, which are the side-menu and sub-menu lines:
.side-menu {
position: fixed;
background: #000;
width: 300px;
height: 100%;
box-sizing: border-box;
padding: 30px 20px;
transition: width .2s ease-in;
}
.side-menu.inactive {
width: 80px;
}
.side-menu .main-menu .sub-menu {
color: #333;
margin-left: 20px;
border-left: 1px solid #666;
box-sizing: border-box;
padding-left: 30px;
max-height: 0;
overflow: hidden;
transition: max-height .2s ease-in;
}
.side-menu .main-menu .sub-menu.active {
max-height: 200px;
}
The height of these cards are variable here I'm using angular material library and bootstrap
I need to reduce the horizontal space between the cards
you can see the cards iron throne and woman's image have taken too much space between them
this is the HTML of item-list component, I'm using Bootstrap and angular material
<div class="row">
<app-item *ngFor="let item of items" [item]="item"></app-item>
</div>
here's the ts code
import {Component, OnDestroy, OnInit} from '#angular/core';
import {Subscription} from "rxjs";
import {Item} from "../../models/item.model";
import {ItemsService} from "../items.service";
#Component({
selector: 'app-item-list',
templateUrl: './item-list.component.html',
styleUrls: ['./item-list.component.css']
})
export class ItemListComponent implements OnInit, OnDestroy {
items: Item[] = [];
isLoading = false;
totalItems = 0;
// itemsPerPage = 2;
// currentPage = 1;
// pageSizeOptions = [1, 2, 5, 10];
private itemsSub: Subscription;
constructor(private itemsService: ItemsService) {
}
ngOnInit(): void {
this.isLoading = true;
this.itemsService.getItems();
this.itemsSub = this.itemsService.itemsUpdateListener.subscribe(items => {
this.isLoading = false
this.items = items
})
}
ngOnDestroy(): void {
this.itemsSub.unsubscribe();
}
}
here's the HTML of ItemComponent
<div class="col-lg-3 col-md-4 col-sm-6">
<div class="card" style="width: 18rem;">
<a routerLink="">
<div class="card-img-wrap"><img class="card-img-top" [src]="item.imagePath" [alt]="item.title"></div>
</a>
<div class="card-body">
<span class="card-title">{{item.title}}</span>
<br>
<span class="card-text" style="margin-right: 3px">Rs. {{item.price}}</span>
</div>
</div>
</div>
in the ts of itemComponent there is only one property
#Input() item: Item;
and here's the css of ItemComponent
.card-img-wrap {
overflow: hidden;
position: relative;
}
.card-img-wrap:after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.2);
opacity: 0;
transition: opacity .6s ease-in-out;
}
.card-img-wrap img {
transition: transform .6s ease-in-out;
width: 100%;
}
.card-img-wrap:hover img {
transform: scale(1.1) ;
}
.card-img-wrap:hover:after {
opacity: 0.5;
}
Im using angular animation builder for creating an animation. I have a grid list with a list of cards. When i click on a button ,i want another div to come on top of the initial card (float in from the left to cover the first card
) .As of now,the div comes in with the animation ,but is showing up on side of the initial card.I have made a stackblitz example to show the current progress.Here is my stackblitz:
https://stackblitz.com/edit/angular-ty4rfh
Also pasting the code here:
import { Component,OnInit,ElementRef } from '#angular/core';
import { trigger, state, style, transition, animate, AnimationBuilder, AnimationPlayer } from '#angular/animations';
export class Asset
{
constructor(public name:string,public description:string){};
}
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
assets:Asset[]=[];
cards=[];
private player: AnimationPlayer;
constructor(private animationBuilder:AnimationBuilder,
private elRef:ElementRef
){}
ngOnInit(){
this.setAssets();
this.setswitch();
}
setAssets(){
this.assets.push(new Asset("Asset1","Latest1"));
this.assets.push(new Asset("Asset2","Latest2"));
this.assets.push(new Asset("Asset3","Latest3"));
this.assets.push(new Asset("Asset4","Latest4"));
this.assets.push(new Asset("Asset5","Latest5"));
this.assets.push(new Asset("Asset6","Latest6"));
this.assets.push(new Asset("Asset7","Latest7"));
this.assets.push(new Asset("Asset8","Latest8"));
this.assets.push(new Asset("Asset9","Latest9"));
for(var i=0; i<this.assets.length;i++){
console.log(this.assets[i].name);
}
}
setswitch() {
for (let i = 0; i < this.assets.length; i++) {
let cardshow = {
id: i.toString(),
isShow : false
};
this.cards.push(cardshow);
}
console.log(this.cards);
}
animate(i){
this.cards[i].isShow=true;
let animationFactory;
animationFactory = this.animationBuilder
.build([
style({ width: '0' }),
animate(200, style({ width: 200 }))
]);
let elem:Element = document.getElementById("div"+(i));
console.log("Elament",elem);
console.log("INDEX",i);
this.player = animationFactory.create(elem);
this.player.play();
}
}
html:
<div>
<mat-grid-list cols="3" rowHeight="3:1">
<mat-grid-tile *ngFor="let asset of assets; index as i">
<div class="border">
<p>{{asset.name}} </p>
<p>{{asset.description}} </p>
<button (click)="animate(i)">click</button>
</div>
<div [ngClass]="!cards[i].isShow?'hide':''" id="{{'div'+i}}" class="border" >
<p>{{asset.description}} </p>
<button>click</button>
</div>
</mat-grid-tile>
</mat-grid-list>
</div>
css:
p {
font-family: Lato;
}
.border{
border:1px solid black;
padding-left: 20px;;
padding-right:20px;
}
.hide{
display:none;
}
EDIT: I need the other card to be completely hidden under the incoming card.Also after clicking on the button inside the incoming card, the animation should be undone(The card should retract back). Also what I'm doing now is increasing the width. What i expect is that the new card should come in from the left sort of like as we see in a drawer
check this stackblitz
app.component.css to be:
p {
font-family: Lato;
}
.border{
border:1px solid black;
padding-left: 20px;;
padding-right:20px;
background: #ffe6ea;
}
.hide{
display:none;
}
.borderPopUp{
padding-left: 20px;;
padding-right:20px;
position:absolute;
background:#d3d3d3d4;
animation: openLikeDrawer 800ms ease-in-out;
width:50%;
left:20%;
}
#keyframes openLikeDrawer{
from {left:0px; }
to { left: 20%; }
}
app.component.html to be :
<div>
<mat-grid-list cols="3" rowHeight="3:1">
<mat-grid-tile *ngFor="let asset of assets; index as i">
<div class="border">
<p>{{asset.name}} </p>
<p>{{asset.description}} </p>
<button (click)="asset.isShow = true">click</button>
</div>
<div *ngIf="asset.isShow" class="borderPopUp" >
<p>{{asset.description}} </p>
<button (click)='asset.isShow = false'>click</button>
</div>
</mat-grid-tile>
</mat-grid-list>
</div>
This is my final solution:
import { Component, OnInit } from '#angular/core';
import { LearningContentService } from '../../../service/learningcontent.service';
import { ActivatedRoute } from '#angular/router'
import { trigger, style, transition, animate, keyframes, query, stagger } from '#angular/animations';
export class Asset {
name: string;
description: string;
cardtoggle:boolean=false;
constructor(rawObj: any) {
this.name = rawObj.name;
this.description = rawObj.description;
this.cardtoggle = rawObj.cardtoggle;
}
}
#Component({
selector: 'app-catalogue',
templateUrl: './catalogue.component.html',
styleUrls: ['./catalogue.component.scss'],
animations: [
trigger('listAnimation', [
transition('* => *', [
query(':enter', style({ opacity: 0 }), { optional: true }),
query(':enter', stagger('50ms', [
animate('1s ease-in', keyframes([
style({ opacity: 0, transform: 'translateY(-75%)', offset: 0 }),
style({ opacity: .5, transform: 'translateY(35px)', offset: 0.3 }),
style({ opacity: 1, transform: 'translateY(0)', offset: 1.0 }),
]))]), { optional: true })
])
],
),
]
})
export class CatalogueComponent implements OnInit {
assets: Array<Asset> = [];
constructor(private route: ActivatedRoute,
private learningService: LearningContentService,
) {
}
ngOnInit() {
this.loadAssets();
}
loadAssets() {
this.learningService.assets().subscribe((res: any) => {
const datas: Array<any> = res;
datas.forEach(asset => this.assets.push(new Asset(asset)));
console.log(this.assets);
});
}
animate(asset) {
asset.cardtoggle = true;
}
close(asset) {
asset.cardtoggle = false;
}
}
html:
<div fxLayout="column">
<div fxLayout="row">
<p class="page-heading" fxFlex="80%">Catalogue</p>
<div class="page-content-filter" fxLayoutAlign="end">
<button mat-button class="search-btn"><i class="material-icons"> search </i></button>
<input class="form-filter" matInput placeholder="Search...">
</div>
</div>
<mat-grid-list gutterSize="20px" cols="3" rowHeight="3:1" [#listAnimation]="assets.length">
<mat-grid-tile *ngFor="let asset of assets">
<div class="full-width white">
<div class="padding20px" fxLayout="column" fxLayoutGap="15px">
<div fxLayout="row" fxLayoutGap="15px">
<img fxFlex="10" style="width:50px;height:50px;" src="../../../../assets/images/images.jpeg" mat-card-image>
<b fxFlex="100">{{asset.name}}</b>
<mat-icon fxLayoutAlign="end end" class="small-icon pointer" (click)="animate(asset)">spa</mat-icon>
</div>
<div fxLayout="row" fxLayoutGap="10px">
<mat-icon class="small-icon">person</mat-icon>
<p class="small-heading"> Mohit Harshan</p>
<mat-divider vertical="true"></mat-divider>
<mat-icon class="small-icon">play_arrow</mat-icon>
<p class="small-heading">Video</p>
<mat-divider vertical="true"></mat-divider>
<mat-icon class="small-icon">face</mat-icon>
<p class="small-heading">5 points</p>
</div>
<mat-progress-bar class="progress" mode="determinate" value="40"></mat-progress-bar>
</div>
</div>
<div class="pull-right" [ngClass]="{'show': asset.cardtoggle }" class="card2">
<div class="padding20px" fxLayout="column" fxLayoutGap="15px">
<div fxLayout="row" fxLayoutGap="15px">
<b fxFlex="100" class="small-heading">{{asset.name}}</b>
<mat-icon fxLayoutAlign="end end" class="small-icon pointer" (click)="close(asset)">clear</mat-icon>
</div>
<div fxLayout="row" fxLayoutGap="10px">
<p class="small">{{asset.description}}</p>
</div>
<div fxLayout="row" fxLayoutGap="10px" fxFLex="100" fxLayoutAlign="end end">
<button fxFLex="100" mat-button class="green-button" >View Details</button>
</div>
</div>
</div>
</mat-grid-tile>
</mat-grid-list>
</div>
css:
.card2{
position:absolute;
z-index: 20;
background: white;
padding-left: 0px;
padding-right:0px;
width:100%;
height:100%;
margin-left: 100%;
transition: margin-left .5s ease-in-out;
}
.show {
margin-left: 0%;
}
.green-button{
border:1px solid lightgreen;
border-radius: 10%;
font-size: 10px;
color:green;
}
I am trying to display a div with the click of a button with slide effect. When something is clicked, it will toggle as shown or invisible with slide effect. I have achieved this so far by doing this.
class App extends Component {
constructor() {
super();
this.state = {
myclass: '',
}
this.divclicked = this.divclicked.bind(this);
}
divclicked() {
if (this.state.myclass === '') {
this.setState({
myclass: 'coolclass'
})
}
else {
this.setState({
myclass: '',
})
}
}
render() {
return (
<div className="App">
<div id="box" onClick={this.divclicked}>
</div>
<div id="seconddiv" className={this.state.myclass}>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
</div>
</div>
);
}
}
export default App;
And my CSS
#box {
height: 50px;
width: 50px;
background-color: red
}
#seconddiv.coolclass{
height:300px;
background: purple;
}
#seconddiv {
height: 0px;
transition: 0.5s;
overflow: hidden;
}
So here, when the red box with the id of "box" is clicked, I give the "seconddiv" a className and CSS takes care of hiding the div. The problem is that when I am giving the className coolclass, I do not want to use px but want to use percentage. So currently, it is going from 0px to 300px. But I want it to go from 0px to 100%; How do I achieve this. When I try to put the height of 100% in seconddiv, the slide animation doesn't work.
You need to set your initial height to 0%. You also need to give .App a height of 100% so that it stretches the full height of the window. In this example, I gave it a static height of 1200px, but that should be determined by the body.
class App extends React.Component {
constructor() {
super();
this.state = {
myclass: '',
}
this.divclicked = this.divclicked.bind(this);
}
divclicked() {
if (this.state.myclass === '') {
this.setState({
myclass: 'coolclass'
})
}
else {
this.setState({
myclass: '',
})
}
}
render() {
return (
<div className="App">
<div id="box" onClick={this.divclicked}>
</div>
<div id="seconddiv" className={this.state.myclass}>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
<p>help help</p>
</div>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'));
#box {
height: 50px;
width: 50px;
background-color: red
}
#seconddiv.coolclass{
max-height:100%;
background: purple;
}
#seconddiv {
max-height: 0%;
transition: 0.5s;
overflow: hidden;
}
.App {
height: 100%;
}
#root {
height: 1200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can just combine conditional rendering ( x && y ) with some scale animation
Example
const scaleAnimationIn = keyframes`
0% {
transform: scale(0, 1);
animation-timing-function: ease-out;
}
100% {
transform: scale(1, 1);
}
`
const QuestionHeaderPausedText = styled.div`
animation: ${scaleAnimationIn} 1s;
animation-delay: 7s;
animation-fill-mode: both;
`
In the return/render method just:
{isSomeConditionTrue && <QuestionHeaderPausedText>
My text
</QuestionHeaderPausedText>}
This example use Styled Components but the method (scaling) will work with any css solution.
I have a left sidebar with position fixed. what I want to achieve is that when I scroll like about 50 or 60 pixel the position of the left sidebar should be changed to fixed.
Left-sidebar.component.ts
import { Component } from '#angular/core';
#Component({
moduleId: module.id,
selector: 'left-sidebar',
templateUrl: 'left-sidebar.html',
styleUrls: ['left-sidebar.scss']
})
export class LeftSideBarComponent {
}
left-sidebar.html
<div class="col-sm-2 left-nav">
</div>
css
.left-nav {
position: absolute;
background: #424242;
height: 100%;
overflow: auto;
padding: 0;
z-index: 1;
}
How to change position of left sidebar from absolute to fixed on scroll?
I suggest you make use of the #HostListner decorator to listen to the scroll event just like this:
import { Inject, HostListener } from "#angular/core";
import { DOCUMENT } from "#angular/platform-browser";
#Component({
moduleId: module.id,
selector: 'left-sidebar',
templateUrl: 'left-sidebar.html',
styleUrls: ['left-sidebar.scss']
})
export class LeftSideBarComponent {
public fixed: boolean = false;
constructor(#Inject(DOCUMENT) private doc: Document) {}
#HostListener("window:scroll", [])
onWindowScroll() {
let num = this.doc.body.scrollTop;
if ( num > 50 ) {
this.fixed = true;
}else if (this.fixed && num < 5) {
this.fixed = false;
}
}
add the .fixed css to your scss file then in your template, you do the following:
<div class="col-sm-2 left-nav" [class.fixed]="fixed">
</div>
I'm using Angular 4 and that solution didn't worked out for me, I tried a different aproach, because num was returning 0 on scroll. ¯_(ツ)_/¯.
I hope this will work out for you as well.
import { Inject, HostListener } from "#angular/core";
#HostListener("window:scroll", ['$event'])
onWindowScroll($event:any) {
let top = window.scrollY;
if (top > 30) {
this.fixed = true;
console.log(top);
} else if (this.fixed && top < 5) {
this.fixed = false;
console.log(top);
}
}
.fixed {
#extend .bg-white;
position: fixed;
top: 80px;
z-index: 999;
}
<div class="container" [class.fixed]="fixed">
<div class="row">
<div class="col-12">
<div ngbDropdown class="dropdown custom-dropdown">
<a class="dropdown-toggle" id="dropdownBasic1" ngbDropdownToggle>Toggle dropdown</a>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<a class="dropdown-item">Action - 1</a>
<a class="dropdown-item">Another Action</a>
<a class="dropdown-item">Something else is here</a>
</div>
</div>
</div>
</div>
</div>