vue3-组件 动画 过渡 复用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>综合练习:组件 过度&动画 可复用性&组合 </title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.31/vue.global.js"></script>
<style>
#app {
border: 1px solid red;
width: 550px;
height: 1500px;
margin: 0 auto;
padding: 0;
text-align: center;
}
#grandfather {
border: 1px solid pink;
margin-top: 100px;
width: 550px;
height: 500px;
margin: 0 auto;
padding: 0;
text-align: center;
}
#lzx {
border: 1px solid purple;
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
#animation1 {
border: 1px solid rgb(28, 14, 28);
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
.abc {
margin: 0 auto;
width: 150px;
height: 150px;
">rgb(0, 0, 0);
transition: all 1.5s;
}
/* 进入之前 */
.sky-enter-from {
width: 0;
height: 0;
">rgb(228, 253, 6);
transform: rotate(180deg);
}
/* 进入过渡之中的状态 */
.sky-enter-active {
transition: all 1.5s ease;
}
/* 进入完成的状态 */
.sky-enter-to {
width: 250px;
height: 250px;
">rgb(238, 216, 216);
}
/* 离开之前 */
.sky-leave-from {
width: 50px;
height: 50px;
">yellow;
}
/* 离开过度之中的状态 */
.sky-leave-avtive {
transition: all 2s ease-out;
}
/* 离开完成之后的状态 */
.sky-leave-to {
transform: rotate(60deg);
">yellow;
width: 0;
height: 0;
}
.aaa {
width: 30px;
height: 50px;
">#008000;
}
.bbb {
transition: all 5s ease;
}
.ccc {
width: 500px;
height: 500px;
">red;
}
.ddd {
width: 300px;
height: 500px;
">#333833;
}
.fff {
transition: all 2s ease;
}
.hhh {
width: 0px;
height: 0px;
">rgb(19, 228, 141);
}
/*多元素过度 */
#animation2 {
border: 1px solid rgb(28, 14, 28);
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
.bac {
width: 100px;
height: 100px;
">red;
margin-left: 50px;
}
.bba {
width: 100px;
height: 100px;
margin-left: 50px;
}
.cba {
width: 100px;
height: 100px;
">rgb(126, 243, 16);
margin-left: 50px;
}
/* .mmm-leave-active {
transition: all 2s ease;
}
.mmm-leave-to {
width: 0;
height: 0;
transform: rotate(30deg);
} */
.leaveActive {
transition: all 1s ease;
}
.leaveTo {
width: 0;
height: 0;
transform: rotate(30deg);
opacity: 0;
}
.enterActive2 {
transition: all .7s ease;
}
.enterFrom {
width: 0;
height: 0;
">green;
opacity: 0;
}
/* 多元素过度 */
#animation3 {
border: 1px solid rgb(28, 14, 28);
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
.aa {
width: 100px;
height: 100px;
}
.bb {
width: 100px;
height: 100px;
}
.enterFrom2 {
opacity: 0;
}
.enterActive3 {
transition: all 2s ease;
}
.leaveTo2 {
opacity: 0;
}
.leaveActive2 {
transition: all 1s ease;
}
/* 多元素列表过度 */
#animation4 {
border: 1px solid rgb(28, 14, 28);
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
#animation4 button {
float: left;
padding: 0 10px;
margin-right: 10px;
}
.skyblue {
float: left;
margin-top: 100px;
padding: 0 10px;
}
.move {
transition: 1s;
}
/* 自定义指令 */
#directive1 {
border: 1px solid rgb(28, 14, 28);
margin-top: 100px;
width: 550px;
height: 1000px;
margin: 0 auto;
padding: 0;
text-align: center;
}
</style>
</head>
<body>
<!-- 组件根组件 -->
<div id="app"></div>
<!-- provide + inject 实现多级传值 -->
<!-- 创建一个爷爷dom -->
<div id="grandfather"> </div>
<!-- vue3写法-->
<div id="lzx">
<div ref="div"></div>
<span ref="span"></span>
<input @click="go" type="button" value="go" />
<lzx></lzx>
</div>
<!-- 动画基础 -->
<!-- style类名必须放在一个顺序里 -->
<div id="animation1">
<input style="margin-bottom:50px" @click="ok=!ok" type="button" name="" value="go" id="">
<div>{{text}}</div>
<div>{{content}}</div>
<!-- 动态改变类名,可以更改动画样式 duration设置生效时间,包含进入时间,离开时间 -->
<transition @before-enter="beforeEnter" @enter="Enter" @after-enter="afterEnter" name="sky" enter-from-class="aaa"
esnter-active-class="bbb" enter-to-class="ccc" leave-from-class="ddd" leave-active-class="fff"
leave-to-class="hhh" :duration="{enter:1000,leave:1000}">
<div v-if="ok" :class="{changeColr:ok}" class="abc"></div>
</transition>
</div>
<!-- 动画:多元素过度 +过渡模式out-in +多组件过度 -->
<div id="animation2">
<button @click="show++">go</button>
<transition mode="out-in" enter-from-class="enterFrom" enter-active-class="enterActive2" leave-to-class="leaveTo"
leave-active-class="leaveActive" name="mmm">
<!-- 多元素过度 -->
<div class="bac" v-if="show==0"></div>
<div class="bba" v-else-if="show==1"></div>
<div class="cba" v-else-if="show==2"></div>
</transition>
<!-- 多组件过度 -->
</div>
<div id="animation3">
<input v-model="view" value="aa" type="radio" checked id="a" /> <label for="a">组件A</label>
<input v-model="view" value="bb" type="radio" id="b" /> <label for="b">组件B</label>
<transition mode="out-in" leave-to-class="leaveTo2" leave-class="leaveActive2" enter-from-class="enterFrom2"
enter-active-class="enterActive3">
<!--多组件过度 -->
<component :is="view"></component>
</transition>
</div>
<!-- 多元素列表过度 transition-group +排序过渡-->
<div id="animation4">
<button @click="add">add</button>
<button @click="remove">remove</button>
<button @click="change">change</button>
<transition-group move-class="move" leave-active-class="enterActive2" leave-to-class="enterFrom2"
enter-active-class="enterActive2" enter-from-class="enterFrom2">
<!-- <div :key="1" v-if="ok">1</div>
<div :key="2" v-if="ok">2</div> -->
<div class="skyblue" v-for="item in arr" :key="item">
{{item}}
</div>
</transition-group>
</div>
<!-- 自定义组件 -->
<div id="directive1">
<input type="text" value="" id="" v-focus>
<input value="add" type="button" @click="num++" />
<p v-color="'red'"> {{num}}</p>
</div>
</body>
<script>
// 二、自定义指令
//1、注册一个全局的自定义指令
const dir = Vue.createApp({
data() {
return {
num: 1
}
},
})
// dir.directive('focus', {
// mounted(el) {
// el.focus()
// }
// })
//自动获取焦点指令
//函数简写
//如果mounted与updata逻辑相同,可以简写,会同时在两个生命周期触发
dir.directive('focus', (el) => {
el.focus()
})
//设置样式指令,动态指令参数
dir.directive('color', (el, binding) => {
//通过等号传递指令
el.style.color = binding.value
console.log(binding);
})
dir.mount('#directive1')
//vue3写法
// const dir = Vue.createApp({
// data() {
// return {
// num: 1
// }
// },
// directives: {
// focus: {
// mounted(el) {
// console.log('mounted');
// el.focus()
// },
// updated() {
// console.log('updated');
// },
// }
// }
// }).mount('#directive1')
// 2、注册一个局部的自定义指令
//注册一个局部自定义指令 v-focus
//指令的定义
//参数有:
//el:传入与指令绑定的元素,可以用来操作dom
//bingding:一个对象,属性有:
//1、name:指令名称 不包括前缀v-
//2、value:指令绑定值:例如v-focus=2;2既是value
//3、oldValue:指令绑定前的一个值。在updata,componentUpdata钩子可用
//4、expression:绑定值的表达式或变量名。例如:v-focus='1+1',expresion 的值是1+1
//5、arg:传给指令的参数。例如v-focus:foo arg的值是foo
//6、modifiers:一个包含修饰符的对象,例如:v-focus.foo.bar modifiers的值为{foo:true,bar:true}
//7、vnode:vue编译生成的虚拟节点
//8、oldVnode:上一个虚拟节点,在updata,componentUpdata钩子可用
//只调用一次,指令与元素绑定时调用,可以定义第一次绑定元素时初始化动作
// bind: function () {
// },
// // inserted钩子函数,被绑定元素在插入父节点时使用
// inserted: function (el) {
// console.log(el);
// el.focus();
// },
// //updata钩子函数,被绑定元素所在的模板更新时调用,不论值是否有变化,通过比较前后的值,可以忽略不必要的膜版更新
// updated() {
// },
// //被绑定元素所在的模板完成一次更新周期时调用
// componentUpdated: function () {
// },
// //只调用一次,指令与元素解绑调用
// unbind: function () {
// }
//三、动画
//#1、动画基础
const animation1 = Vue.createApp({
"data": function () {
return {
"ok": true,
"text": "",
"content": ""
}
},
"methods": {
//动画生命周期
beforeEnter: function (el) {
this.text = "进入之前"
console.log(1);
},
//enter的done参数,after参数失效 起作用为跳过css动画
Enter: function (el) {
// done();
this.content = "进入之中"
console.log(2);
},
afterEnter: function () {
//根据持续时间duration去调用,此处为1000ms
console.log(3);
},
beforeLeave: function (el) {
this.text = "离开之前"
console.log(4);
},
//enter的done参数,after参数失效 起作用为跳过css动画
leave: function (el) {
// done();
this.content = "离开之中"
console.log(5);
},
afterLeave: function () {
//根据持续时间duration去调用,此处为1000ms
console.log(6);
},
},
}).mount('#animation1')
//#2、动画多元素过度
const animation2 = Vue.createApp({
data() {
return {
"show": 0,
}
},
components: {
},
methods: {
},
}).mount('#animation2')
//#3、多组件过度
const animation3 = Vue.createApp({
data() {
return {
"view": "aa",
}
},
//不可同行盒子作为组件模板,可以用包括多个子盒子作为模板
components: {
"aa": {
"template": `
<div class='aa'>
<div>
我是组件A1
</div>
<div>
我是组件A2
</div>
</div>`
},
"bb": {
"template": ` <div class='bb'> 我是组件B </div>`
}
},
methods: {
},
}).mount("#animation3")
//#4、多元素列表过度
const animation4 = Vue.createApp({
data() {
return {
"ok": true,
"arr": [1, 2, 3, 4, 5, 6, 7, 8, 9],
"num": 10
}
},
methods: {
add() {
this.arr.splice(parseInt(Math.random() * this.arr.length), 0, this.num)
this.num++
console.log(parseInt(Math.random() * this.arr.length));
},
remove() {
this.arr.splice(parseInt(Math.random() * this.arr.length), 1)
},
change() {
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var arr2 = [];
for (let index = 0; index < 9; index++) {
arr2.push(arr.splice(parseInt(Math.random() * arr.length), 1)[0])
}
console.log(arr2);
this.arr = arr2;
}
},
component: {
},
}).mount('#animation4')
//ref用法,作用:找到指定标签的内容、标签
const vue3 = Vue.createApp({
"data": function () {
return {}
},
"methods": {
go() {
this.$refs.div.innerText = '修改了div'; //<div>修改了div</div>
console.log(this.$refs.div); //<div></div>
}
},
"components": {
"lzx": {
"data": function () {
return {
"msg": "massage",
}
},
"template": `
{{msg}}
`
}
}
}).mount('#lzx');
//#provide+inject 父级多级传值 爷爷组件给孙子组件传值
//创建一个爷爷实例
const grandfather = Vue.createApp({
provide: {
promsg: 'pro信息,直接传递给孙子组件'
},
data() {
return {
msg: '我是grandfather的信息'
}
},
//用于局部子组件的注册,全局组件不需要注册
components: {
},
// 用于规定爷爷组件模板,其还可以放置全局子组件或注册过的局部组件
template: `
<div> 1我是grandfather组件 </div>
<p>--------------</p>
<father :msg = 'msg' > </father>
<p>--------------</p>
`
})
grandfather.component('father', {
props: ['msg'],
template: `
<div > 2我是father组件 </div>
<p>--------------</p>
<p style='color:red'> 4father接收到: {{msg}} </p>
<p>--------------</p>
<child :msg='this.msg'></child>
`
})
grandfather.component('child', {
props: ['msg'],
inject: ['promsg'],
template: `
<div> 3我是child组件 </div>
<p>--------------</p>
<p style='color:pink'>5child收到 {{msg}}</p>
<p style='color:purple'>5child收到 {{promsg}} </p>
`
})
//将grandfather实例挂载到爷爷dom
const gf = grandfather.mount('#grandfather')
//一、全局与局部的组件注册与使用
//2、局部组件
//新的变量 在新变量里面定义组件模板 而不是使用app.compontent,局部组件只能在局部,需要使用的话,需要在全局根组件注册
const counter = {
data() {
return {
count: 0
}
},
template: `
<div>
<h1> 局部组件 </h1>
<button @click="count++"> 您点击了{{count}}次 </button>
</div> `
}
const liZongXiao = {
data() {
return {
name: 'lzx'
}
},
template: `<div>
<h1> 命名规则 </h1>
</div> `
}
//1、全局组件
//#第一步定义基础实例模板,创建vue的实例
const app = Vue.createApp({
data() {
return {
showComponent: 'sister1',
msg: 'lzx',
total: 0,
name: "lzx12",
pay: () => {
alert('父组件消息弹出')
}
}
},
methods: {
handleAppCounter(num) {
this.total = num
// console.log(num)
},
swithImg() {
//每调用一次改变一次值
this.showComponent = this.showComponent === 'sister1' ? 'sister2' : 'sister1';
}
},
//#注册局部组件
components: {
// 必须在全局根组件中,注册后才能使用
// counter 简写 可以直接注册局部组件
//或根据命名规范取名
counter,
liZongXiao
},
//使用全局子组件:全部全局子组件,拼成了大的根组件
template: ` <top-title></top-title>
<describe></describe>
<sum></sum>
<sum></sum>
<sum></sum>
<counter></counter>
<li-zong-xiao></li-zong-xiao>
<son :name='name'></son>
<daughter :pay='pay'></daughter>
<girl :name='name'></girl>
<boy :name='name'></boy>
<total :total='total'></total>
<hello style='color:red;' :msg='msg' ></hello>
<chang-total :total1='total' @add='handleAppCounter'> </chang-total>
<teacher >
<div style='color:red'> {{msg}} </div>
<div> <student> </student> </div>
</teacher>
<schoolmate > </schoolmate>
<roommate>
<template #one>
<div> 具名插槽1 </div>
</template>
<template #two>
<div> 具名插槽3 </div>
</template>
</roommate>
<brother v-slot='props' >
<span> {{props.item}}</span>
</brother>
<div style='margin-top:15px'> 动态组件component 配合keep-alive保存虚拟DOM的数据</div>
<keep-alive>
<component :is='showComponent'> </component>
</keep-alive>
<div> <button @click='swithImg'> 切换图片 </button> </div>
<asyncComponent> </asyncComponent>
`
})
//全局子组件 不需要再父根组件components对象中注册,则可以使用
//#父传子
app.component('son', {
//props接收在全局template中传递的值,下列使用在子组件的template
props: ['name'],
//静态传值只能传递字符串,建议还是用动态传值
template: `<div>{{typeof name}} </div>`
})
//#子组件调用父组件的函数
app.component('daughter', {
//props可以接收一个父组件中在template中绑定过来的的函数,使用this指针调用
props: ['pay'],
methods: {
handleClick() {
alert('点击子组件调用父组件方法')
this.pay()
}
},
template: `
<div @click='this.handleClick'>点击子组件弹出父组件的信息 </div>
`
})
//#传值类型的校验
app.component('girl', {
props: {
//支持校验类型:字符串类型 布尔类型 数组类型 对象类型 函数类型 占位符
name: String
},
data() {
return {
}
},
template: `<div>
{{name}}
</div>`
})
//#父组件必须在其template中传值,不然抛出警告
app.component('boy', {
props: {
name: {
//此处规定传入值的值必须为字符串,也可以是其他类型
type: String,
//validator后写函数
validator: function (value) {
//value=name值
//校验value是否含有lzx字符串,如果不含有,则为-1
return value.search('lzx') != -1
},
//必填校验
//required: true,
//用户不填可以给予默认值
default: '默认值'
}
},
template: `
<div>
{{name}}
</div>
`
})
//#单项数据流:数据从父组件传递到子组件,
app.component('total', {
data() {
return {
//子组件不能直接改变父组件传递过来的值,如果需要修改,则需要通过另一个变量作为介质
newTotal: this.total
}
},
props: ['total'],
template: `
{{newTotal}}
<button @click='newTotal+=1'>增加数量</button>
`
})
//#Non-props 子组件没有接受父组件传递过来的参数 而子组件完全复制了父组件template中定义的模板
app.component('hello', {
mounted() {
//在业务逻辑中的使用
// console.log(this.$attrs.msg);
},
//在组件元素上使用
//规定是否接受父组件中绑定过来的参数,一般为样式或者类名,如果子组件中template存在父盒子包含子盒子,则子盒子负责接受其参数
//如果需要指定盒子进行接受参数,则可以通过v-bind='$attrs'绑定,但需要将父盒子去掉 例如第一个h4
//$attrs可以指定需要绑定的值,例如第二个h4
inheritAttrs: true,
// props: ['msg'],
template: `
<h4 v-bind='$attrs'> Hello word!1</h4>
<h4 v-bind:style='$attrs.style' >Hello word!2</h4>
<h4>Hello word!3</h4>
`
})
//#子传父:子组件$emit调用父组件方法,传值($emit传递参数),更改值(不需要介质,可以直接修改),校验
app.component('changTotal', {
props: ['total1'],
//声明了所有从父组件调用的函数,并进行校验
emits: {
add: (value) => {
//校验value==检验父组件传递来的this.total1
return value < 20 ? true : false
}
},
data() {
return {
}
},
methods: {
Clicksonhandle() {
//使用$emit方法调用父组件的方法
//同时可以向父组件传递参数
//直接修改父组件传递传递来的值
//操作逻辑都在子组件进行,父子解耦
this.$emit('add', this.total1 + 3)
}
},
template: `
<div >
子组件接受父组件的值:
{{total1}}
<button @click='Clicksonhandle'> add</button>
</div>
`
})
//#组件插槽-Slot:
app.component('teacher', {
template: `<div>
<span>
<slot> </slot>
</span>
</div>`
})
//#在插槽中直接使用子组件
app.component('student', {
template: `<span style="color:pink; font-size:50px "> student </span>`
})
//#插槽作为备用内容:如果父组件给予内容样式,则优先显示父组件内容、样式,否则使用插槽内的默认内容
app.component('schoolmate', {
template: `
<div>
<slot> <span style='color:purple'>默认显示内容 </span> </slot>
</div>
`
})
//#插槽的具名插槽的使用:给插槽命名,在父组件的template中使用template模板指定插槽位置
app.component('roommate', {
template: `
<div>
<slot name='one'></slot>
<div> 具名插槽2 </div>
<slot name='two'> </slot>
</div>
`
})
//#插槽的作用域:在子组件使用v-slot的数据,使用插槽的方式可以传递给父组件,在插槽绑定需要传递的值,在使用键值对的方式传递,然后在父组件tempalate中v-slot绑定键值对
app.component('brother', {
data() {
return {
list: ['作用域1', '作用域2', '作用域3']
}
},
template: `
<div>
<ul>
<li v-for="(item,index) in list">
{{item}}
</li>
</ul>
<slot v-for='(item,index) in list' :item='item' > </slot>
</div>
`
})
//#动态组件 <component :is='showComponent'></component>
app.component('sister1', {
template: `
<img src='/tianmao/images/H1.jpg'>
`
})
app.component('sister2', {
// <img src='/tianmao/images/H4.jpg'>
template: `
<input/>
`
})
//#异步组件 作用:1、在异步请求远端数据时,使用异步组件缓加载数据 2、拆分大型网页应用
app.component('asyncComponent', Vue.defineAsyncComponent(
() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div> 这是一个异步组件 </div>`
})
}, 3000);
})
}
))
//#全局子组件:使用便利,但会影响性能,局部的组件,可以按需导入
app.component('top-title', {
template: '<h2>标题</h2>'
})
//#全局子组件
app.component('describe', {
template: '<div>主体</div>'
})
//#全局子组件的复用
app.component('sum', {
//data必须是一个函数,其闭包保证了每个复用组件的独立性
data() {
return {
sum: 0
}
},
template: `
<div>
<button @click="sum++">您点击了{{sum}}次</button>
</div>
`
})
//#第二步将实例挂载到dom树
const vm = app.mount("#app")
</script>
</html>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!