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
Related
Simply put, I want to change the color of the first two texts of each line to white, but it doesn't work anyway. It is as if the span elements generated by js cannot be affected by css.
Please see the picture for the specific code.
Sorry I'm not very good at using Stack Overflow yet, the code has been added.
export default {
mounted(){
console.log("Hello!")
let list = document.querySelectorAll('.shinchou-menu li a')
list.forEach( link => {
let letters = link.textContent.split("");
link.textContent = "";
letters.forEach((words, i) => {
let span = document.createElement("span");
span.textContent = words
if(i < 2){
span.className = "highlight"
}
span.style.transitionDelay = `${i/10}`
link.append(span);
})
})
}
}
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #fafafa;
}
</style>
<style lang="less" scoped>
.shinchou-menu {
--heightlight-text-color: #00ACF0;
list-style: none;
li {
a {
text-decoration: none;
display: inline-flex;
background: #000;
font-size: 1.6em;
font-weight: 700;
color: var(--heightlight-text-color);
padding: 4px;
margin: 6px 0;
span.highlight {
color: #FFF;
}
}
}
}
</style>
<template>
<div>
<ul class="shinchou-menu">
<li>ニュース</li>
<li>ストーリー</li>
<li>スターフ&キャスト</li>
<li>キャラクター</li>
<li>放送·配信情報</li>
</ul>
</div>
</template>
Don't manipulate DOM directly!
Vue keeps a separate DOM structure (called virtual DOM) where it tracks all elements for reactivity. Whenever something reactive changes, the DOM node in the actual DOM of the page gets re-rendered. Why? Because it's a lot faster than tracking changes in DOM.
What this means is that whenever you change DOM directly, you will lose those mods whenever Vue re-renders.
You are supposed to handle your data in the component and allow Vue to render it using template structural directives (v-if, v-for, etc...).
In your case, that would look something like this (not sure what the word separator is):
Vue2:
new Vue({
el: '#app',
data: () => ({
items: [
'ニュース',
'ストーリー',
'スターフ&キャスト',
'キャラクター',
'放送·配信情報'
]
})
})
.highlighted { color: red }
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<div id="app">
<ul>
<li v-for="item in items"
:class="{ highlighted: item.split('·').length > 1 }"
v-text="item" />
</ul>
</div>
Vue3:
Vue.createApp({
setup: () => ({
items: [
'ニュース',
'ストーリー',
'スターフ&キャスト',
'キャラクター',
'放送·配信情報'
]
})
}).mount('#app')
.highlighted { color: red }
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="app">
<ul>
<li v-for="item in items"
:class="{ highlighted: item.split('·').length > 1 }"
v-text="item" />
</ul>
</div>
Note: you can (and maybe should) create a method taking the item as a param and returning the correct classes for the item. This way you don't have to write your js into the template.
<li v-for="item in items" :class="itemClasses(item)">
component:
/* Options API syntax: */
methods: {
itemClasses(item) {
/* return string, array of strings or class object
docs: https://vuejs.org/guide/essentials/class-and-style.html
*/
}
}
/* (alternative) Composition API syntax : */
setup() {
// replace this function with your own logic
const itemClasses = item => item.split('·').length > 1;
return { itemClasses }
}
Long story short, Vue requires some trickery when debugging height and #scroll issues within componenets. The best call of action is to find what element is scrolling and move the scroll to the child, in my case.
So, how can I find what element is scrolling without countless additions/removals of event listeners?
You can get the element that you're scrolling on through the scroll event that gets passed to the scroll handler with event.target. I've included a snippet below which should hopefully help your case scenario.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: "#app",
methods: {
scrollHandler(ev) {
console.log(ev.target.id);
}
}
});
#app > div > div {
height: 200px;
overflow-y: scroll;
max-height: 100px;
display:block;
}
h1 {
font-size: 3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<div :id="i" v-for="i in 5" :key="i" #scroll="scrollHandler">
<h1>{{i}}</h1>
</div>
</div>
</div>
I'm trying to create with React.js a type of scroll like this one: http://spassky-fischer.fr/, where two divs are scrolling in inverse directions. They are using transform: translateY(), and I tried to implement it as well but I don't get where I'm wrong here. Here's the architecture of the projet. The current version is also here: http://noiseless-tendency.surge.sh/
App.js:
ComponentDidMount(){
window.addEventListener("scroll", this.scrollHandler);
}
...
scrollHandler = () => {
this.setState({
scrollPositionY: window.scrollY
})
}
...
render() {
return (
<div className="App">
<MainItemsContainer {...this.state} />
</div>
);
}
MainItemsContainer.js:
render() {
let style_first = {
transform: `translateY(${this.props.scrollPositionY})`,
overflow: "hidden"
}
let style_second = {
transform: `translateY(-${this.props.scrollPositionY})`,
overflow: "hidden"
}
return (
<div className="main_items_container">
<section
style={style_first}
className="main_items_container_child">
<ItemsContainer {...this.props}/>
</section>
<section
style={style_second}
className="main_items_container_child">
<ItemsContainer {...this.props}/>
</section>
</div>
);
}
App.css:
.main_items_container {
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
}
.main_items_container .main_items_container_child{
width: 50%;
height: 100vh;
overflow: scroll;
}
The sample site you linked actually uses the wheel event rather than the scroll event. It looks like they use this library to accomplish it: https://swiperjs.com/demos/ . My understanding is that the scroll event only fires if there's a scrollbar, which is why your event handler didn't fire.
I've created a Code Sandbox that produces the effect you want in React. It does rely on jQuery to compute the height of the whole element, and to set the initial transformation for the left half. However, those are just convenience methods, and if you don't want jQuery as a dependency, you can find workarounds for those pretty easily.
So you could use hooks and pass a custom css var through inline styling on state change to update your translateY. I have not tested but I hope you get my drift.
let elRef = useRef(null)
let[ height, setHeight] = useState()
use useLayoutEffect hook to add the event listener
useLayoutEffect (()=>{
if(!elRef) return
elRef.current.addEventListener("scroll", setHeight(elRef.current.scrollY));
return elRef.current.removeEventListener("scroll", setHeight());
}, )
and put the ref on you outermost div perhaps , so your outer div would look like
<div className='container' ref={elRef} style{{ --height: height}} >
<div className='columOne' > </div>
<div className='columTwo' > </div>
</div>
in your css (I haven't put all that is required but just showing how you custom css var is used
.container{
display: flex;
flex-direction: row
}
And within that equal sized columns
.columnOne{
transform: `translateY(calc(var(--height) * 1px)`,
overflow: "hidden"
}
.columnTwo{
transform: `translateY(-calc(var(--height) * 1px)`,
overflow: "hidden"
}
Does that help. Let me know if I could be more clear. Or use styled components and pass a prop in to achieve the same result.
I'm creating a web game with different levels and need to change a div style using code stored inside an array in AngularJS. The game has two screens: the left one the user types the code in HTML and, the right one shows the result. This screen has a background and a div with the element created by the code. Each level has a different style for the background, so it needs to be loaded when the user changes the current level.
The HTML code is as follows:
<div id="background" ng-class="css"></div>
And the code in AngularJS is:
$scope.css = [
{
//level one
'background-image': 'url(url1)',
'position' : 'absolute',
...
},
{
//level two
'background-image': 'url(url2)',
...
}];
Another approach is to use a variable called $scope.cur_level that I'm using get number of current level to use it in ng-class with conditions:
ng-class="cur_level = '0' ? 'level_one' : 'level_two'"
In this case, each level style was created in the CSS file. However, the style of the first level is loaded for every level. I really want use the array approach. What is the best way to do this?
Here's the correct way of using the ng-class directive:
(function () {
"use strict";
const app = angular.module("app", []);
app.controller("app.GameCtrl", $scope => {
$scope.currentLevel = "1";
$scope.setLevel = () => {
$scope.currentLevel = $scope.currentLevel === '1' ? '2' : '1';
};
}
);
})();
.container {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
width: 100px;
height: 100px;
color: #FFF;
font-size: 3em;
}
.level-one {
background-color: red;
}
.level-two {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app" ng-controller="app.GameCtrl">
<div class="container" ng-class="{'level-one': currentLevel === '1', 'level-two': currentLevel === '2'}">
{{currentLevel}}
</div>
Change Level
</div>
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.