I have the following component which will simply fetch a dataset of skills containing a title and a percentage from my database.
My goal is to have the initial width value on each div be 0% and once the http request has come back and the correct information is retreived, set the correct percentage for each skill with an animation that goes from left to right (the divs will have a background-color so you should see its width grow, ex.: 0% ---> 95%).
I'm wondering what would be the best approach to accomplish this in a correct Angular 2 treatment. I know there's an animation property you can use on the component decorator but I'm not sure as to how to make it work with the async results coming in.
The most important thing about this question is how to handle the data that comes in through the async pipe in a way that I can show an animation for the percentage bump. Form 0 to whatever it will be. As it is now I immediately see the final result, however, an animation is never executed (and the anim is what I'm actually looking for, not just printing the final bar result).
I'm sure there are a few different ways to get this working, just confused as to which would be the best.
Here's my component:
import { Component, OnInit } from '#angular/core'
import { SkillsService } from './skills.service'
#Component({
selector: 'skills',
template: `
<section class="skills">
<div *ngFor="let skill of skillsService.skills$ | async">
<div class="skills__bar" [style.width]="skill.percentage + '%'">
<h3 class="skills__title">{{skill.title}}</h3>
</div>
</div>
</section>
`,
})
export class SkillsComponent implements OnInit {
constructor(private skillsService: SkillsService) {}
ngOnInit() {
this.skillsService.fetchSkills()
}
}
Thanks in advance, all!
You can use a CSS transition like so:
//Demo purposes only.
$("#get-skills-btn").on("click", function() {
var randomValue = Math.random();
$(".skills__percentage").css("transform", "scaleX(" + randomValue + ")");
});
.skills__bar {
background-color: silver;
text-align: center;
position: relative;
}
.skills__percentage {
background-color: green;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: scaleX(0);
transform-origin: left;
transition: transform 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.skills__title {
position: relative;
z-index: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="skills">
<div>
<div class="skills__bar">
<div class="skills__percentage"></div>
<h3 class="skills__title">{{skill.title}}</h3>
</div>
</div>
</section>
<button id="get-skills-btn">
Fetch Data
</button>
For angular use something like this:
[style.transform]="scaleX(skill.percentage/100)"
With Angular 4.2+, we can do that using #angular/animations.
<div [#listParent]="len" class="list">
<div *ngFor="let item of itemsAsync | async" [style.width]="item.val" class="item">
{{item.name}}
</div>
</div>
In component decorator, listParentanimation is defined like:
animations: [
trigger('listParent', [
transition('* => *', [
query(':enter', style({ width: 0 })),
query(':enter', animate('1.6s', style({ width: '*'})))
])
])
listParentanimation will be triggered whenever component property len changes.
In this plunker, try click Get one or Get multiple button.
Related
Hello everyone~ At present, I have just come into contact with vue. There is a project that needs to make the input box higher after clicking the button, but I have difficulty writing the effect!
I would like to ask everyone to help me see if there is a mistake in the code. ? Thank you all for your help!
HTML
<div id="myApp">
<textarea class="message" placeholder="pleace enter you message" :class="expand_message"></textarea>
click
</div>
CSS
.message{
display: block;
}
.expand_message{
height: 300px;
}
JavaScript
let myApp = new Vue({
el:"#myApp",
data:{
expand_message:true
},
methods:{
btnClick: function(){
// 點擊變紅色與回復原狀
this.expand_message = !this.expand_message;
},
},
});
My program template
so first off try to keep code on stackoverflow. e.g. in your question.
<div id="myApp">
<textarea placeholder="pleace enter you message" :class="['message', expand_message ? 'expand_message' : null]"></textarea>
<button #click="btnClick">click</button>
</div>
And to get your animation, you can not transition on height however you can transition on max height:
.message{
display: block;
max-height: 40px;
height: 300px;
transition: max-height ease-out 1s;
}
.expand_message{
max-height: 300px;
}
Try this:
:class="{'expand_message': expand_message}"
I've been trying to mock a carousel-like effect in cards rendered through v-for. I have an array of data and I have a method that left rotates that array. I'm passing that rotated array in v-for. But, the rotated arrays shift the real dom div instead of re-rendering the component in v-for (I think this is how Vue behaves for optimization). I've tried transition-group but it only applies transition to leaving and entering div. Is there any way so that I can get a carousel-like effect (divs moving upward) using Vue transition? When I was writing the code, the divs were moving upward and behaving as expected because at that time instead of divs shifting in real dom, only the data inside that div were changing but later on it started to behave like this (divs shifting in real dom)
Here is the fiddle link: https://jsfiddle.net/aanish/7pe5jq9u/4/
Please help me to achieve the expected behavior.
var Box = {
props: ['achievement'],
template: '<transition name="slide-up"><div class="box" :key="achievement">{{achievement}}</div></transition>',
};
new Vue({
el: '#app',
data: {
achievements: ['Title1', 'Title2', 'Title3', 'Title4']
},
created() {
this.autoRotateArr();
},
components: {
'box': Box
},
methods: {
leftRotateArr() {
this.achievements.push(
this.achievements.shift()
)},
autoRotateArr() {
this.interval = setInterval(() => this.leftRotateArr(), 3000);
}
}
})
.box-wrapper {
display: flex;
flex-direction: column;
margin: 0 auto;
padding: 50px;
}
.box {
height: 50px;
background-color: orange;
margin: 10px 0;
}
.slide-up-enter-active {
transition: all 666ms cubic-bezier(0, 0, 1, 1);
}
.slide-up-enter {
transform: translateY(50px);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="hero">
<box :achievement="achievements[0]"></box>
</div>
<div class="box-wrapper">
<div><button #click="leftRotateArr">Up</button></div>
<box v-for="achievement in achievements" :achievement="achievement" :key="achievement"></box>
</div>
</div>
.
From the help of Michal Levý comment above, I get the expected behavior using
transition-group
css
.image-darken {
transition: 1s;
filter: brightness (10%);
}
javascript
const portfolioItems = document.querySelectorAll('.portfolio-item-wrapper');
portfolioItems.forEach(portfolioItem => {
portfolioItem.addEventListener('mouseover', () => {
console.log(portfolioItem.childNodes[1].classList);
portfolioItem.childNodes[1].classList.add('image-darken');
});
});
HTML
<div class="portfolio-item-wrapper">
<div class="portfolio-img-background"
style="background-image:url(images/portfolio1.jpg"></div>
<div class="img-text-wrapper">
<div class="logo-wrapper">
<img src="images/quip.png">
</div>
<div class="subtitle">
I built the Quip Ecommerce platform, named a Top 25 Invention by Time
Magazine in 2016.
</div>
</div>
</div>
I want to use javascript to make it so that when I put my cursor over the image, it darkens in one second. And when my cursor leaves, it lightens in one second. I have many portfolio-item-wrapper elements. I want to use javascript because I want this to be my introduction to javascript. I am following a tutorial on youtube.
This is the tutorial video. The part with javascript comes in at about 1:14:00.
This is what the website looks like so far:
Please help me and dumb it down for me, i just started learning to code.
Thanks!
What you're aiming for is typically achieved without any JavaScript, by using the :hover CSS pseudo-class. Taking a non-JavaScript "pure CSS" approach generally allows for simpler solution that is more maintainable in the long run.
In your case, a pure CSS approach is possible by removing your JavaScript and by applying the following changes to your CSS:
/*
Not needed
.image-darken {
transition: 1s;
filter: brightness (10%);
}
*/
.portfolio-item-wrapper {
/* Add transition rule to filter property of the item wrapper */
transition: filter 1s;
}
/* Add styling that applies when the user "hovers" the element. The
"hover" will cause the filtering to be applied to this element */
.portfolio-item-wrapper:hover {
filter: brightness(10%);
}
/* Added for snippet - not needed in your code */
.portfolio-img-background {
min-height:5rem;
}
<div class="portfolio-item-wrapper">
<div class="portfolio-img-background"
style="background-image:url(https://via.placeholder.com/150)"></div>
<div class="img-text-wrapper">
<div class="subtitle">
I built the Quip Ecommerce platform, named a Top 25 Invention by Time
Magazine in 2016.
</div>
</div>
</div>
If you really want to take a scripted approach to this, then you could do the following:
document.querySelectorAll('.portfolio-item-wrapper')
.forEach(item => {
/* Get background element of this item */
const background = item.querySelector('.portfolio-img-background')
/* Add image-darken class to background element on hover event */
item.addEventListener('mouseover', () => {
background.classList.add('image-darken');
});
/* Add image-darken class to background element on hover end */
item.addEventListener('mouseout', () => {
background.classList.remove('image-darken');
});
});
/* Apply transition to the background element of portfolio item */
.portfolio-item-wrapper .portfolio-img-background {
transition: filter 1s;
}
/* Define image darkening */
.image-darken {
filter: brightness(10%);
}
/* Added for snippet - not needed in your code */
.portfolio-img-background {
min-height:5rem;
}
<div class="portfolio-item-wrapper">
<div class="portfolio-img-background"
style="background-image:url(https://via.placeholder.com/150)"></div>
<div class="img-text-wrapper">
<div class="subtitle">
I built the Quip Ecommerce platform, named a Top 25 Invention by Time
Magazine in 2016.
</div>
</div>
</div>
Hope that helps!
Another way would be to overlay the image with a grey overlay that shall enable you to have dark effect on the image in the background.
.layer {
background-color: rgba(248, 247, 216, 0.7);
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.card-title {
position: absolute;
top: 36%;
font-size: 2.0em;
width: 100%;
font-weight: bold;
color: #fff;
}
.card {
position: relative;
text-align: center;
}
HTMl Patch
<div class="card" id="card">
<img src="{{Path_to_featured_image}}"/>
<div class="card-title" id="card-title">{{Heading_or_slug}}</div>
</div>
JS
$( "#card-title" ).hover(
function() {
$( this ).addClass( "layer" );
}, function() {
$( this ).removeClass( "layer" );
}
);
you can also set unset the classes on realtime using Javascript if you want to control it dynamically using various parameters like id, class or even tag to select the effected element.
I advise you use JavaScript addEventListener 'hover', function(){getEl......ById....style.opacity=0.5}
First look at my code to understand the problem.
<template>
<div class="header"
:class="flat ? 'flat' : null"
:class="app ? 'app' : null">
</div>
</template>
<script>
export default {
props: {
flat: {
type: Boolean,
default: false
},
app: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="scss">
.header {
width: 100%;
height: 55px;
background: white;
box-shadow: 0px 3px 6px #ccc;
transition: .8s ease-in-out;
}
.flat {
box-shadow: none;
}
.app {
padding-left: 10%;
padding-right: 10%;
}
</style>
so as you can see here do i have my flat prop that will trigger a flat class to show a box-shadow or not. But i also want someone to trigger the app prop that will put some padding in the header.
the problem here is dat you can't put multiple :classes in a element.
is there any solution for this?
There are several ways to achieve what you're trying to do, Vue is great at this.
1. Pass an array of classes
<div
class="header"
:class="[flat ? 'flat' : null, app ? 'app' : null]"
></div>
2. Pass an object
<div
class="header"
:class="{flat: flat, app: app}"
></div>
Here, only the props that have a truthy value will be set as classes.
2.1 If you're using ES6
You can use the object property value shorthand
<div
class="header"
:class="{flat, app}"
></div>
Bonus
You can also mix 1 and 2 if necessary (I've needed it sometimes)
<div
class="header"
:class="[{flat, app}, someOtherClass]"
></div>
Try to combine them in the same class attribute as follows:
<div class="header"
:class="{ 'flat':flat,'app' : app}"
>header</div>
See the official documentation
You can create a method that returns the same object as #Boussadjra Barhim answer.
//if value is evaluated into true, the key will be a part of the class
setClass: function(flat, app){
return {
flat: flat,
app: app
}
}
Use it via
<element :class="setClass(flat, app)" />
But in this case you can write other longer code (without uglifying the template) to process the values before returning an object
setClass: function(flat, app){
//do something else with inputs here
return {
flat: flat,
app: app
}
}
I'm building an accordion in Angular 2 and I what I want to accomplish is when I click on one of the accordions, the accordion will open and all other opened accordions will be closed (so basically the previously opened accordion will be closed). Without DOM manipulation.
How I'm doing this now now: I have a type boolean named 'classActive' which I toggle on true or false on click.
(note: the accordion is being looped by using ngFor)
HTML
<div class="accordion" [ngClass]="{'is-active' : classActive}">
<div class="accordion__container">
<div class="accordion__header" (click)="toggleClass($event)">
<h2 class="accordion__heading">{{ name }}</h2>
</div>
<div class="accordion__content">
{{ content }}
</div>
</div>
</div>
JS
#Component({
selector: 'accordion',
templateUrl: './accordion.component.html',
styleUrls: ['./accordion.component.scss']
})
export class AccordionComponent implements OnInit {
classActive: boolean = false;
toggleClass(event) {
event.preventDefault();
this.classActive = !this.classActive;
}
}
SCSS
.accordion {
height: 100px;
overflow: hidden;
transition: height .3s cubic-bezier(1, 0, .41, 1.01);
&.is-active {
height: 200px;
.accordion__content {
opacity: 1;
}
}
&__header {
cursor: pointer;
height: 100px;
padding: 0 $p-container;
position: relative;
}
&__heading {
text-transform: lowercase;
line-height: 100px;
pointer-events: none;
}
&__content {
opacity: 0;
transition: opacity .6s;
padding: $p-container;
}
}
I think the solution would be to set all 'classActive' on false and then set the clicked one on true. But is that possible?
The problem, as mentioned in comments, is that you've created a single variable to handle multiple elements.
The simplest solution IMHO is to modify your function to "save" the current index clicked as below:
Component:
activeIndex: number = 0; // The accordion at index 0 will be open by default
toggleClass(i: number): void {
this.activeIndex = i;
}
So, in your template you can just modify your [ngClass] to:
[ngClass]="{'is-active': activeIndex === i}"
Template:
<div *ngFor="let yourVariable of yourArray, let i = index">
<div class="accordion" [ngClass]="{'is-active': activeIndex === i}">
<div class="mood__container">
<div class="mood__header" (click)="toggleClass(i)">
<h2 class="mood__heading">{{ name }}</h2>
</div>
<div class="accordion__container>
{{ content }}
</div>
</div>
</div>
</div>
Check the complete code below:
DEMO
[ngClass]="{'is-active' : activeIndex === i, 'is-dactive' : activeIndex != i}">
togglePopover(event, i: number) {
event.preventDefault();
this.activeIndex = i
console.log(this.activeIndex);
}