深入響應(yīng)式
- 追蹤變化:
把普通js對(duì)象傳給Vue實(shí)例的data選項(xiàng),Vue將使用Object.defineProperty把屬性轉(zhuǎn)化為getter/setter(因此不支持IE8及以下),每個(gè)組件實(shí)例都有相應(yīng)的watcher實(shí)例對(duì)象,它把屬性記錄為依賴,當(dāng)依賴項(xiàng)的setter被調(diào)用的時(shí)候,watcher會(huì)被通知并重新計(jì)算,從而更新渲染
變化檢測(cè):
Vue不能檢測(cè)到對(duì)象屬性的添加或刪除,因此屬性必須在data對(duì)象上存在才能讓Vue轉(zhuǎn)換它,才是響應(yīng)式的
Vue不允許在已經(jīng)創(chuàng)建的實(shí)例上動(dòng)態(tài)添加新的根級(jí)響應(yīng)式屬性,但是可以將響應(yīng)屬性添加到嵌套的對(duì)象上,使用Vue.set(object,key,value)
Vue.set(vm.someObject,'b',2)
還可以用vm.$set實(shí)例方法,是Vue.set的別名:
this.$set(this.someObject,'b',2)
想向已有對(duì)象添加一些屬性,可以創(chuàng)建一個(gè)新的對(duì)象,讓它包含原對(duì)象的屬性和新屬性:
this.someObject=Object.assign({},this.someObject,{a:1,b:2})
- 聲明響應(yīng)式屬性:由于不允許動(dòng)態(tài)添加根級(jí)響應(yīng)式屬性,所以初始化實(shí)例前要聲明,即便是個(gè)空值:
var vm = new Vue({
data: {
// 聲明 message 為一個(gè)空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后設(shè)置 `message`
vm.message = 'Hello!'
- Vue是異步更新隊(duì)列的,目的是緩沖同一個(gè)事件循環(huán)中所有數(shù)據(jù)變化去除重復(fù)數(shù)據(jù),但是問(wèn)題來(lái)了,當(dāng)設(shè)置數(shù)據(jù)變化時(shí),并不會(huì)立即重新渲染,需要排隊(duì),可是如果想要在這個(gè)變化后緊接著做點(diǎn)什么,就需要一個(gè)Vue.nextTick(callback)來(lái)表明,這個(gè)更新后再執(zhí)行操作:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改數(shù)據(jù)
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
//組件上使用nextTick
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: 'not updated'
}
},
methods: {
updateMessage: function () {
this.message = 'updated'
console.log(this.$el.textContent) // => '沒(méi)有更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '更新完成'
})
}
}
})
過(guò)渡效果
用transition封裝組件,添加過(guò)渡,需要有個(gè)name屬性,會(huì)自動(dòng)生成四個(gè)對(duì)應(yīng)類(name為transition的name屬性的值)
- name-enter——?jiǎng)赢?huà)起點(diǎn)
- name-enter-active——?jiǎng)赢?huà)中點(diǎn)
- name-leave——?jiǎng)赢?huà)中點(diǎn)的下一幀(默認(rèn)與上一幀相同)
- name-leave-active——?jiǎng)赢?huà)終點(diǎn)
(這是Vue官網(wǎng)的圖)
css過(guò)渡(簡(jiǎn)單的transition)
<div id="app-01">
<button v-on:click="show=!show">toggle</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
var app01=new Vue({
el:'#app-01',
data:{
show:true
}
})
//css部分,設(shè)置對(duì)應(yīng)類的動(dòng)畫(huà)
.fade-enter-active,.fade-leave-active{
transition: opacity 3s;
}
.fade-enter, .fade-leave-active{
opacity: 0
}
css動(dòng)畫(huà)(animation)
//代碼雖然多,但是信息量并不大
//css部分
.bounce-enter-active{
animation: bounce-in 1s
}
.bounce-leave-active{
animation: bounce-out 1s
}
//這里給p設(shè)了一個(gè)背景色,還設(shè)了一個(gè)50%的寬度,是為了觀測(cè)scale的動(dòng)畫(huà)效果
p{
background-color: red;
width:50%;
}
@keyframes bounce-in{
0%{
transform: scale(0)
}
50%{
transform:scale(1.5)
}
100%{
transform: scale(1)
}
}
@keyframes bounce-out{
0%{
transform: scale(1)
}
50%{
transform:scale(1.5)
}
100%{
transform: scale(0)
}
}
//html部分
<div id="app-02">
<button v-on:click="show=!show">toggle</button>
<transition name="bounce">
<p v-if="show">look at me</p>
</transition>
</div>
//js部分
var app02=new Vue({
el:'#app-02',
data:{
show:true
}
})
與css過(guò)渡的區(qū)別:‘在動(dòng)畫(huà)中 v-enter 類名在節(jié)點(diǎn)插入 DOM 后不會(huì)立即刪除,而是在 animationend 事件觸發(fā)時(shí)刪除。’(存疑)
- 自定義過(guò)渡類名:結(jié)合其他第三方動(dòng)畫(huà)庫(kù)等使用
- enter-class
- enter-active-class
- leave-class
- leave-active-class
//用法
<transition name="bounce"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight">
<p v-if="show">look at me</p>
</transition>
- 使用js鉤子,enter和leave各自有4個(gè)鉤子:
- beforeEnter——一些預(yù)設(shè)
- enter——進(jìn)入動(dòng)畫(huà),部分情況需要有回調(diào)函數(shù)
- afterEnter
- enterCancelled
- beforeLeave
- leave——離開(kāi)動(dòng)畫(huà),部分情況需要有回調(diào)函數(shù)
- afterLeave
- leaveCancelled
注:只用js過(guò)渡時(shí),enter和leave中的回調(diào)函數(shù)是必須的,不然動(dòng)畫(huà)會(huì)立即完成。對(duì)于僅適用js過(guò)渡的元素,最好添加v-bind:css="false"避免過(guò)渡中css的影響
//引入一個(gè)velocity庫(kù),便于操作dom屬性
<script src="vue/velocity.min.js"></script>
<div id="app-03">
<button v-on:click="show=!show">toggle</button>
<transition
//綁定一個(gè)beforeEnter函數(shù),用來(lái)預(yù)設(shè)狀態(tài)
v-on:before-enter="beforeEnter"
//這是進(jìn)入動(dòng)畫(huà)
v-on:enter="enter"
//這是離開(kāi)動(dòng)畫(huà)
v-on:leave="leave"
//排除css影響
v-bind:css="false"
>
<p v-if="show">demo</p>
</transition>
</div>
var app03=new Vue({
el:'#app-03',
data:{
show:false
},
methods:{
beforeEnter:function(el){
el.style.opacity = 0
//預(yù)設(shè)了旋轉(zhuǎn)中心點(diǎn)是左側(cè)
el.style.transformOrigin='left'
},
enter:function(el,done){
//字體由初始變?yōu)?.4em,耗時(shí)300毫秒
Velocity(el,
{opacity:1,fontSize:'1.4em'},
{duration:300})
//字體變回1em,并變?yōu)殪o止?fàn)顟B(tài)
Velocity(el,{fontSize:'1em'},{complete:done})
},
leave:function(el,done){
//結(jié)束動(dòng)作分為三步:先是旋轉(zhuǎn)50度,x軸偏移15pxs是為了使旋轉(zhuǎn)看起來(lái)更自然,并設(shè)了600毫秒的時(shí)間
Velocity(el,{translateX:'15px',rotateZ:'50deg'},{duration:600})
//第二步:旋轉(zhuǎn)了100度,循環(huán)2次
Velocity(el,{rotateZ:'100deg'},{loop:2})
//第三次旋轉(zhuǎn)45度,并結(jié)束
Velocity(el,{
rotateZ:'45deg',
translateX:'30px',
translateY:'30px',
opacity:0
},{complete:done})
}
}
})
- rotateZ——3d旋轉(zhuǎn),繞z軸旋轉(zhuǎn)
- translateX——x軸變化
- translateY——y軸變化
- transformOrigin——變化旋轉(zhuǎn)元素的基點(diǎn)(圓心)
初始渲染的過(guò)渡
這里例子沒(méi)效果,(存疑)
.custom-appear-class{
font-size: 40px;
color: red;
background: green;
}
.custom-appear-active-class{
background: green;
}
<div id="app-03">
<button v-on:click="show=!show">toggle</button>
<transition
appear
appear-class="custom-appear-class"
appear-active-class="custom-appear-active-class"
>
<p v-if="show">demo</p>
</transition>
</div>
var app03=new Vue({
el:'#app-03',
data:{
show:true
}
})
多個(gè)元素的過(guò)渡
- 用v-if/v-else來(lái)控制過(guò)渡
<transition>
<table v-if="items.length > 0">
</table>
<p v-else>Sorry, no items found.</p>
</transition>
但是需要注意,當(dāng)兩個(gè)過(guò)渡元素標(biāo)簽名相同時(shí),需要設(shè)置key值來(lái)區(qū)分,否則Vue會(huì)自動(dòng)優(yōu)化為,只替換內(nèi)容,那么就看不到過(guò)渡效果了
先來(lái)個(gè)過(guò)渡的例子:
//html部分,設(shè)定了兩個(gè)button,并用toggle來(lái)切換值,為了避免Vue的只替換內(nèi)容,設(shè)了兩個(gè)不同的key值
<div id="app-04">
<button v-on:click="isEditing=!isEditing">toggle</button>
<br>
<transition name="try" mode="in-out">
<button v-if="isEditing" key="save">
in
</button>
<button v-else key="edit">out</button>
</transition>
</div>
//css部分
//首先是對(duì)過(guò)渡元素進(jìn)行絕對(duì)定位,不然過(guò)渡過(guò)程中,元素共同出現(xiàn)時(shí)位置會(huì)有奇怪的問(wèn)題(這個(gè)限定有點(diǎn)麻煩)
#app-04 button{
position: absolute;
left:100px;
}
//參考案例,button進(jìn)入有個(gè)動(dòng)畫(huà),取名move_in
.try-enter-active{
animation: move_in 1s;
}
//動(dòng)畫(huà)包含了位移,從右側(cè)到中間,透明度從0到1
@keyframes move_in{
from{left:150px;opacity: 0}
to{left:100px;opacity: 1}
}
//button出去的動(dòng)畫(huà)取名move_out
.try-leave-active{
animation:move_out 1s;
//同move_in
@keyframes move_out{
from{left:100px;opacity: 1}
to{left:50px;opacity: 0}
}
//js部分
var app04=new Vue({
el:'#app-04',
data:{
isEditing:true
}
})
多種方法設(shè)置不同標(biāo)簽的過(guò)渡:
- 通過(guò)給同一個(gè)元素的key特性設(shè)置不同的狀態(tài)來(lái)代替v-if/v-else(這個(gè)好棒):
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
- 把v-if升級(jí)為switch,實(shí)現(xiàn)不止2個(gè)標(biāo)簽的綁定:
<transition>
<button v-bind:key="docState">
{{ buttonMessage }}
</button>
</transition>
computed: {
buttonMessage: function () {
switch (docState) {
case 'saved': return 'Edit'
case 'edited': return 'Save'
case 'editing': return 'Cancel'
}
}
}
- vue還提供了過(guò)渡模式,兩種,in-out和out-in,用法:
<transition name="fade" mode="out-in">
<!-- ... the buttons ... -->
</transition>
算是過(guò)渡控制增強(qiáng)
多組件過(guò)渡 :動(dòng)態(tài)組件component綁定
//css部分
.component-fade-enter-active,.component-fade-leave-active{
transition:opacity .5s ease;
}
.component-fade-enter,.component-fade-leave-active{
opacity: 0;
}
//html部分
<div id="app-05">
<button v-on:click="view=='v-a'?view='v-b':view='v-a'">toggle</button>
<br>
//設(shè)置了out-in后組件可以不用考慮絕對(duì)定位的問(wèn)題了
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
</div>
//js部分
var app05=new Vue({
el:'#app-05',
data:{
view:'v-a'
},
components:{
'v-a':{
template:'<div>Component A</div>'
},
'v-b':{
template:'<div>Component B</div>'
}
}
})
列表過(guò)渡
使用transition-group,必須對(duì)子項(xiàng)設(shè)置特定的key名
- 進(jìn)入離開(kāi)過(guò)渡
//css部分
#app-06 p{
width: 100%
}
#app-06 span{
//把display設(shè)置成inline-block,才可以設(shè)置它的translateY,不然沒(méi)有位移效果
display: inline-block;
margin-right: 10px;
}
.list-enter-active,.list-leave-active{
transition: all 1s;
}
.list-enter,.list-leave-active{
opacity: 0;
//兩種寫(xiě)法,一種是如下,另一種是translateY(30px)
transform: translate(0px,30px);
}
//html部分
<div id="app-06">
<button @click='add'>Add</button>
<button @click='remove'>Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" :key="item">
{{item}}
</span>
</transition-group>
</div>
//js部分
var app06=new Vue({
el:'#app-06',
data:{
items:[1,2,3,4,5,6,7,8,9],
nextNum:10
},
methods:{
randomIndex:function(){
return Math.floor(Math.random()*this.items.length)
},
//復(fù)習(xí)一下splice(a,b,c),a:待增加/刪除的項(xiàng)目,b:刪除的項(xiàng)目數(shù),c:待增加的項(xiàng)目(可不止一個(gè))
add:function(){
this.items.splice(this.randomIndex(),0,this.nextNum++)
},
remove:function(){
this.items.splice(this.randomIndex(),1)
}
}
})
- 列表的位移過(guò)渡
使用新增的v-move特性,它會(huì)在元素的改變定位的過(guò)程中應(yīng)用,vue內(nèi)部是使用了一個(gè)叫FLIP的動(dòng)畫(huà)隊(duì)列。
//css部分
//這里對(duì)name-move設(shè)了一個(gè)transition,就可以控制位移過(guò)程中的動(dòng)畫(huà)效果
.shuffle-list-move{
transition: transform 1s;
}
//html部分
<div id="app-07">
<button @click='shuffle'>Shuffle</button>
<transition-group name="shuffle-list" tag="ul">
<li v-for="item in items" :key="item">
{{item}}
</li>
</transition-group>
</div>
//js部分
var app07=new Vue({
el:'#app-07',
data:{
items:[1,2,3,4,5,6,7,8,9]
},
methods:{
//教程中是引用了lodash的方法庫(kù),讓我們自己寫(xiě)這個(gè)洗牌算法吧(Fisher-Yates shuffle)
shuffle:function(){
let m=this.items.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.items[i];
this.items.splice(i,1,this.items[m]);
this.items.splice(m,1,t);
}
}
}
})
- 進(jìn)入離開(kāi)過(guò)渡和位移過(guò)渡的組合版:
//css部分
#app-06 p{
width: 100%
}
#app-06 span{
display: inline-block;
margin-right: 10px;
transition: all 1s;
}
.list-enter,.list-leave-active{
opacity: 0;
transform: translate(0px,30px);
}
//需要重點(diǎn)注意的是這里:離開(kāi)動(dòng)畫(huà)需要設(shè)置一個(gè)絕對(duì)定位,不然離開(kāi)動(dòng)畫(huà)不圓滑,原因不明(存疑)
.list-leave-active{
position: absolute;
}
.list-move{
transition: transform 1s;
}
//html部分
<div id="app-06">
<button @click='add'>Add</button>
<button @click='remove'>Remove</button>
<button @click='shuffle'>Shuffle</button>
<transition-group name="list" tag="p">
<span v-for="item in items" :key="item">
{{item}}
</span>
</transition-group>
</div>
//js部分
var app06=new Vue({
el:'#app-06',
data:{
items:[1,2,3,4,5,6,7,8,9],
nextNum:10
},
methods:{
randomIndex:function(){
return Math.floor(Math.random()*this.items.length)
},
add:function(){
this.items.splice(this.randomIndex(),0,this.nextNum++)
},
remove:function(){
this.items.splice(this.randomIndex(),1)
},
shuffle:function(){
let m=this.items.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.items[i];
this.items.splice(i,1,this.items[m]);
this.items.splice(m,1,t);
}
}
}
})
- 列表升級(jí)版——矩陣?yán)?/li>
//css部分
//由于shuffle是整個(gè)矩陣混排,所以其實(shí)是一個(gè)長(zhǎng)度為81的列表的混排,矩陣的位置由css的flex來(lái)確定
//父元素規(guī)定為flex,規(guī)定長(zhǎng)度,并定義了超出長(zhǎng)度時(shí)的換行方式
.cellContainer{
display: flex;
flex-wrap: wrap;
width: 238px;
margin-top: 10px;
}
//子元素規(guī)定為flex,規(guī)定長(zhǎng)寬,橫向?qū)R方式,縱向?qū)R方式,為了視覺(jué)好看,重合部分的邊需要去重。
.cell{
display: flex;
justify-content: space-around;
align-items: center;
width: 25px;
height: 25px;
border: 1px solid #aaa;
margin-right: -1px;
margin-bottom: -1px;
}
.shuffle-table-move{
transition: transform 1s;
}
//html部分
<div id="app-08">
<button @click='shuffle'>Shuffle</button>
<transition-group name="shuffle-table" tag="div" class="cellContainer">
<div v-for="cell in cells" :key="cell.id" class="cell">
{{cell.number}}
</div>
</div>
//js部分
var app08=new Vue({
el:'#app-08',
data:{
//數(shù)組方法,先是創(chuàng)建一個(gè)有81項(xiàng)的數(shù)組,內(nèi)容為null,然后用map方法返回每個(gè)數(shù)組項(xiàng),包含id和number兩個(gè)屬性
cells:Array.apply(null,{length:81})
.map(function(_,index){
return{
id:index,
number:index%9+1
}
})
},
methods:{
shuffle:function(){
let m=this.cells.length,
t,i;
while(m){
i=Math.floor(Math.random()*m--);
t=this.cells[i];
this.cells.splice(i,1,this.cells[m]);
this.cells.splice(m,1,t);
}
}
}
})
- 列表的漸進(jìn)過(guò)渡
核心思想是設(shè)置一個(gè)定時(shí)器,根據(jù)index設(shè)置不同的位移序列,從而形成漸進(jìn)
<div id="app-09">
<input type="text" v-model="query">
//:css禁止css的影響
//監(jiān)聽(tīng)事件:before-enter/enter/leave
<transition-group name="staggered-fade" tag="ul" v-bind:css="false" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:leave="leave"
>
<li v-for="(item,index) in computedList" v-bind:key="item.msg" v-bind:data-index="index">{{item.msg}}</li>
</transition-group>
</div>
var app09=new Vue({
el:'#app-09',
data:{
//原始列表
query:'',
list:[
{msg:'Bruce Lee'},
{msg:'Jackie Chan'},
{msg:'Chuck Norris'},
{msg:'Jet Li'},
{msg:'Kung Fury'}
]
},
computed:{
//復(fù)合列表,用了一個(gè)過(guò)濾器,返回查找query不為空的選項(xiàng)
computedList:function(){
var vm=this
return this.list.filter(function(item){
return item.msg.toLowerCase().indexOf(vm.query.toLowerCase())!==-1
})
}
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
el.style.height=0
},
//設(shè)置一個(gè)delay時(shí)間,根據(jù)參數(shù)值而不同
enter:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(el,{opacity:1,height:'1.6em'},{complete:done})
},delay)
},
leave:function(el,done){
var delay=el.dataset.index*150
setTimeout(function(){
Velocity(el,{opacity:0,height:0},{complete:done})
},delay)
}
}
})
h5自定義屬性dataset用法:
- html中自定義屬性:
<div id="example" data-pro="我是pro"></div> - js中引用屬性:
var div =document.getElementById(''example")
console.log(div.dataset.pro)
//我是pro
注意兩者的小差異,html中是data-name,js中引用時(shí)需要寫(xiě)為dataset.name
可復(fù)用的過(guò)渡
這里提到了函數(shù)式組件,需要看完后面的render函數(shù)來(lái)結(jié)合使用
動(dòng)態(tài)過(guò)渡
過(guò)渡的數(shù)據(jù)可以動(dòng)態(tài)控制,用js來(lái)獲取
<div id="app-10">
//input type="range"是滑動(dòng)條,把值綁定到fadeInDuation
Fade In<input type="range" v-model="fadeInDuation" min="0" v-bind:max="maxFadeDuration">
Fade Out<input type="range" v-model="fadeOutDuation" min="0" v-bind:max="maxFadeDuration">
<transition v-bind:css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave">
//這里有一個(gè)對(duì)show值的判斷,這是控制淡入淡出循環(huán)的關(guān)鍵
<p v-if="show">hello</p>
</transition>
<button @click="stop=true">Stop it!</button>
</div>
var app10=new Vue({
el:'#app-10',
data:{
show:true,
fadeInDuation:1000,
fadeOutDuation:1000,
maxFadeDuration:1500,
stop:false
},
mounted:function(){
this.show=false
},
methods:{
beforeEnter:function(el){
el.style.opacity=0
},
enter:function(el,done){
var vm=this
Velocity(el,{opacity:1},{duration:this.fadeInDuation,complete:function(){
done()
if(!vm.stop) vm.show=false
}})
},
leave:function(el,done){
var vm=this
Velocity(el,{opacity:0},{duration:this.fadeOutDuation,complete:function(){
done()
vm.show=true
}})
}
}
})
注意問(wèn)題:
1、初始show設(shè)置為true,mounted鉤子里又改為false,而enter和leave中又分別對(duì)show有更新,為什么這么復(fù)雜?
:測(cè)試發(fā)現(xiàn),初始第一次渲染,并不會(huì)觸發(fā)enter事件,而是默認(rèn)渲染(無(wú)語(yǔ)),如果沒(méi)有mounted鉤子的show=false,則無(wú)法觸發(fā)leave事件,元素會(huì)停留在初始渲染狀態(tài),不會(huì)自循環(huán),所以整個(gè)循環(huán)是從mounted觸發(fā)leave事件開(kāi)始的,leave事件又把show=true,轉(zhuǎn)而觸發(fā)enter事件,enter事件show=false,又觸發(fā)leave,從而形成循環(huán)
2、之前都沒(méi)太注意Velocity前的那句var vm=this,原因是進(jìn)入Velocity函數(shù)后,在done語(yǔ)句之后,this就不是Vue自己的this了,所以需要存值,done之前的目測(cè)還可以用
過(guò)渡狀態(tài)
- 狀態(tài)動(dòng)畫(huà)與watcher
<script src="vue/tween.js"></script>
<div id="app-01">
<input type="number" v-model.number="number" step="20">
<p>{{animatedNumber}}</p>
</div>
var app01=new Vue({
el:'#app-01',
data:{
number:0,
animatedNumber:0
},
watch:{
number:function(newValue,oldValue){
var vm=this
function animate(time){
requestAnimationFrame(animate)
TWEEN.update(time)
}
new TWEEN.Tween({tweeningNumber:oldValue})
.easing(TWEEN.Easing.Quadratic.Out)
.to({tweeningNumber:newValue},1000)
.onUpdate(function(){
vm.animatedNumber=this.tweeningNumber.toFixed(0)
})
.start()
animate()
}
}
})
注意問(wèn)題
1、百度搜tweenjs,出來(lái)的那個(gè)creatjs并不是教程里引用的庫(kù),google的是:git倉(cāng)庫(kù)地址
研究了一圈用法,發(fā)現(xiàn)用法很基本,很固定:
- 創(chuàng)建一個(gè)tween對(duì)象,并傳入起始對(duì)象:new TWEEN.Tween(起始對(duì)象)
- (此項(xiàng)非必須)變化曲線方程:easing(曲線方程),官方提供了31種
- to(終點(diǎn)對(duì)象,時(shí)間)
- (此項(xiàng)非必須但此案例必須)onUpdate(函數(shù)),此案例中就是每次變化,都要執(zhí)行函數(shù),從而才形成動(dòng)畫(huà)效果
- start()開(kāi)始
- 重點(diǎn)來(lái)了,tween并不會(huì)自啟動(dòng),需要用update()來(lái)啟動(dòng),官方也建議加一個(gè)requestAnimationFrame,以平滑動(dòng)畫(huà),防止掉幀,于是出現(xiàn)了啰嗦但是必須的animate()函數(shù)。
2、上文提到的requestAnimationFrame,字面意思是“請(qǐng)求動(dòng)畫(huà)幀”,它的用途張?chǎng)涡翊笊褚呀?jīng)詳細(xì)說(shuō)明,附鏈接:張?chǎng)涡癫┛?/a>,概括說(shuō)明是,requestAnimationFrame(內(nèi)容)在下一幀執(zhí)行動(dòng)畫(huà),與setTimeout的區(qū)別是不會(huì)掉幀。
3、v-model.number="number"我愣了一下,后來(lái)發(fā)現(xiàn)是后綴標(biāo)記,表示把v-model綁定值轉(zhuǎn)化為數(shù)字
- 進(jìn)化版,引入顏色漸變動(dòng)畫(huà)
//引入新庫(kù),color.js
<script src="vue/color.js"></script>
//對(duì)色塊樣式簡(jiǎn)單定義
<style>
#app-02 span{
display: block;
width:100px;
height: 100px;
}
</style>
//html部分
<div id="app-02">
//v-on:keyup.enter="updateColor"是綁定一個(gè)鍵盤(pán)按鍵,.enter是13鍵的別名
<input v-model="colorQuery" v-on:keyup.enter="updateColor" placeholder="Enter a color">
<button v-on:click="updateColor">Update</button>
<p>Preview:</p>
//把樣式綁定到tweenedCSSColor
<span v-bind:style="{backgroundColor:tweenedCSSColor}"></span>
<p>{{tweenedCSSColor}}</p>
</div>
//js部分
//又見(jiàn)命名空間,作者叫brehaut
var Color=net.brehaut.Color
var app02=new Vue({
el:'#app-02',
data:{
colorQuery:'',
//注意color是一個(gè)包含4個(gè)屬性的對(duì)象
color:{
red:0,
green:0,
blue:0,
alpha:1
},
tweenedColor:{}
},
//這里用了一個(gè)原生js的方法,Object.assign(目標(biāo)對(duì)象,源對(duì)象),是將源對(duì)象的可枚舉屬性復(fù)制進(jìn)目標(biāo)對(duì)象內(nèi),按值復(fù)制,返回目標(biāo)對(duì)象,一般用于合并多個(gè)對(duì)象,此例中只有一個(gè)對(duì)象,改為this.tweenedColor=this.color也是ok的,或者不用created鉤子,在data內(nèi)初始化tweenedColor也ok
created:function(){
this.tweenedColor=Object.assign({},this.color)
},
//watch很有用,每當(dāng)color值有變化,都會(huì)觸發(fā)這個(gè)函數(shù)
watch:{
color:function(){
function animate(time){
requestAnimationFrame(animate)
TWEEN.update(time)
}
//這里,tweenedColor也會(huì)被更新掉
new TWEEN.Tween(this.tweenedColor)
.to(this.color,750)
.start()
animate()
}
},
computed:{
//這里用了color.js的一個(gè)方法,toCSS(),是把color形式的對(duì)象轉(zhuǎn)化為可用的"#123456"的顏色字符串
tweenedCSSColor:function(){
return new Color({
red:this.tweenedColor.red,
green:this.tweenedColor.green,
blue:this.tweenedColor.blue,
alpha:this.tweenedColor.alpha
}).toCSS()
}
},
methods:{
updateColor:function(){
//使用了color.js的一個(gè)方法toRGB(),創(chuàng)建新的color對(duì)象,傳入有效值,轉(zhuǎn)化為color格式的對(duì)象并返回
this.color=new Color(this.colorQuery).toRGB()
//這一句是清空了input的上一次輸入,可有可無(wú),去掉的話input內(nèi)會(huì)保持上一次輸入的值
this.colorQuery=''
}
}
})
總結(jié)
1、color.js用到的方法:
創(chuàng)建新的color對(duì)象并轉(zhuǎn)成color格式{red:1,green:2,blue:3,alpha:1}:new Color(有效值).toRGB()
轉(zhuǎn)成#123456格式:new Color(有效值).toCSS()
動(dòng)態(tài)狀態(tài)轉(zhuǎn)換
//css部分
#app-03 svg,#app-03 input{
display: block;
}
//polygon的填色方式不同于其他css語(yǔ)法,用的是fill
#app-03 polygon{
fill:#41b883;
}
//stroke控制邊的樣式
#app-03 circle{
fill:transparent;
stroke: #35495e;
}
#app-03 input{
width: 90%;
margin-bottom: 15px;
}
//html部分
<div id="app-03">
<svg width="200" height="200">
<polygon v-bind:points="points"></polygon>
<circle cx="100" cy="100" r="90"></circle>
</svg>
<label>Sides: {{sides}}</label>
<input type="range" min="3" max="500" v-model.number="sides">
<label>Minimum Radius: {{minRadius}}%</label>
<input type="range" min="0" max="90" v-model.number="minRadius">
<label>Update Interval: {{updateInterval}} milliseconds</label>
<input type="range" min="10" max="2000" v-model.number="updateInterval">
</div>
//js部分
var app03=new Vue({
el:'#app-03',
data:function(){
var defaultSides=10
var stats=Array.apply(null,{length:defaultSides}).map(function(){return 100})
return {
stats:stats,
points:generatePoints(stats),
sides:defaultSides,
minRadius:50,
interval:null,
updateInterval:500
}
},
watch:{
//這里有個(gè)好玩的問(wèn)題,參考fiddle上的例子,是用了一個(gè)for循環(huán)來(lái)添加刪改stats,但是我想啊,watch作為監(jiān)控,應(yīng)該是每次sides有變化就會(huì)觸發(fā),而sides本身又是一個(gè)滑條,那么數(shù)值必然是依次變化的,所以可否取消for循環(huán),只判斷新舊值,每次只添加刪除一項(xiàng)。
//但是通過(guò)console.log(newSides-oldSides)發(fā)現(xiàn),滑動(dòng)太快的話,就會(huì)輸出2啊3啊什么的,這可能和瀏覽器讀取還有watch的監(jiān)控機(jī)制有關(guān)
//于是只好乖乖改為for循環(huán)了
sides:function(newSides,oldSides){
var sidesDifference = newSides - oldSides
if (sidesDifference > 0) {
this.stats.push(this.newRandomValue())
} else {
this.stats.shift()
}
},
//咦,引用了一個(gè)新的緩動(dòng)庫(kù),感覺(jué)這個(gè)庫(kù)好用多了,是著名的Greensock綠襪子
stats:function(newStats){
TweenLite.to(
this.$data,
this.updateInterval/1000,
{points:generatePoints(newStats)})
},
updateInterval:function(){
this.resetInterval()
}
},
//清掉這個(gè)能看到預(yù)設(shè)的初始狀態(tài)
mounted:function(){
this.resetInterval()
},
methods:{
randomizeStats:function(){
var vm=this
this.stats=this.stats.map(function(){
return vm.newRandomValue()
})
},
newRandomValue:function(){
return Math.ceil(this.minRadius+Math.random()*(100-this.minRadius))
},
resetInterval:function(){
var vm=this
clearInterval(this.interval)
this.randomizeStats()
this.interval=setInterval(function(){
vm.randomizeStats()
},this.updateInterval)
}
}
})
function valueToPoint(value,index,total){
var x=0,y=-value*0.9,angle=Math.PI*2/total*index,cos=Math.cos(angle),sin=Math.sin(angle),tx=x*cos-y*sin+100,ty=x*sin+y*cos+100
return {x:tx,y:ty}
}
function generatePoints(stats){
var total=stats.length
return stats.map(function(stat,index){
var point=valueToPoint(stat,index,total)
return point.x+','+point.y
}).join(' ')
}
注意問(wèn)題:
1、用了svg多邊形和圓,多邊形傳入點(diǎn)的參數(shù),點(diǎn)的參數(shù)格式為{x,y x,y}
<svg>
<polygon v-bind:points="points"></polygon>
<circle cx="100" cy="100" r="90"></circle>
</svg>
2、引用了一個(gè)新庫(kù),叫TweenLite.js,還有一個(gè)系列的動(dòng)畫(huà)庫(kù),簡(jiǎn)單好用
3、data返回了一個(gè)函數(shù),是為了不同實(shí)例不共享數(shù)據(jù)
4、sides監(jiān)控處出現(xiàn)一個(gè)想當(dāng)然的問(wèn)題,見(jiàn)代碼解釋
5、動(dòng)畫(huà)的循環(huán)是通過(guò)resetInterval中的定時(shí)器實(shí)現(xiàn)的,動(dòng)畫(huà)的漸變是監(jiān)控了stats的變化。
render函數(shù)
第一個(gè)例子
<div id="app-01">
<anchored-heading :level="2">Hello world!</anchored-heading>
</div>
Vue.component('anchored-heading',{
render:function(createElement){
return createElement(
'h'+this.level,
this.$slots.default)
},
props:{
level:{
type:Number,
required:true
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:''
}
})
完整例子
<div id="app-01">
<anchored-heading :level="1">
hello world
</anchored-heading>
</div>
//創(chuàng)建一個(gè)查找并遞歸子文本的函數(shù)
var getChildrenTextContent=function (children){
return children.map(function(node){
return node.children?getChildrenTextContent(node.children):node.text
}).join('')
}
Vue.component('anchored-heading',{
render:function(createElement){
var headingId=getChildrenTextContent(this.$slots.default)
.toLowerCase()
//把非字符替換成'-'
.replace(/\W+/g,'-')
//把開(kāi)頭結(jié)尾的‘-’替換為空
.replace(/(^\-|\-$)/g,'')
return createElement(
'h'+this.level,
[
createElement('a',{
attrs:{
name:headingId,
href:'#'+headingId
}
},this.$slots.default)
]
)
},
props:{
level:{
type:Number,
required:true
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:''
}
})
深入分析createElement()
- 三部分組成:
- 標(biāo)簽名,必須,此例中是'h'+this.level和'a'
- data object參數(shù),不是必須的,{},即各種屬性設(shè)定,class/style/attrs/props/domProps/on/nativeOn/directives/scopedSlots/slot/key/ref(class和style級(jí)別最高)
- 子節(jié)點(diǎn)或者內(nèi)容,必須,如果是子節(jié)點(diǎn),因?yàn)椴恢挂粋€(gè),所以需要加一個(gè)[]表示為數(shù)組形式,(但是每個(gè)子元素必須唯一)子節(jié)點(diǎn)就是嵌套的createElement(),如果是內(nèi)容,直接就是字符串,例子是,this.$slots.default。
- render的形式
render:function(createElement){
一些預(yù)操作
return createElement(組成內(nèi)容)
使用js來(lái)代替模板功能
- v-if&v-for變?yōu)樵膇f/else&for(map)
<div id="app-01">
<component-vif :items="items"></component-vif>
</div>
Vue.component('component-vif',{
props:["items"],
//由于items使用了map和length,所以應(yīng)該為一個(gè)數(shù)組對(duì)象,且包含name屬性
render:function(createElement){
if(this.items.length){
return createElement('ul',this.items.map(function(item){
return createElement('li',item.name)
}))
} else{
return createElement('p','No items found.')
}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:'1',
items:[
{name:'aaa'},
{name:'bbb'}
]
}
})
- v-model要自己實(shí)現(xiàn)相應(yīng)邏輯
<div id="app-01">
<component-vmodel :orivalue="value"></component-vmodel>
</div>
Vue.component('component-vmodel',{
render:function(createElement){
var self=this
return createElement('p',[
createElement('input',{
domProps:{
value:self.value,
},
on:{
input:function(event){
self.value=event.target.value
}
}
}),
createElement('span',self.value)
])
},
props:['orivalue'],
data:function(){
var value=this.orivalue
return {value}
}
})
var app01=new Vue({
el:'#app-01',
data:{
level:'1',
items:[
{name:'aaa'},
{name:'bbb'}
],
value:''
}
})
注意問(wèn)題
由于存在對(duì)組件內(nèi)value賦值的問(wèn)題,第一次只有prop沒(méi)有data的時(shí)候,后臺(tái)友好提示,“Avoid mutating a prop directly since the value will be overwritten”,于是加一個(gè)data進(jìn)行一次賦值,這里用了函數(shù)式data
原例子中組件只有一個(gè)input元素,然而怎么看出來(lái)綁定成功沒(méi)有呢?我加了一個(gè)span來(lái)看值的對(duì)應(yīng)修改,這里發(fā)現(xiàn),屬性上domProps下設(shè)置innerHTML和第三項(xiàng)上內(nèi)容綁定,目測(cè)沒(méi)什么區(qū)別嘛
事件&按鍵修飾符
** .capture/.once**——對(duì)應(yīng)!/~,可組合
capture是捕獲的意思,capture模式是捕獲階段觸發(fā)回調(diào),區(qū)別于默認(rèn)的冒泡階段,這個(gè)解釋segmentfault上有個(gè)很好的例子-
其他修飾符沒(méi)前綴,可以自己使用事件方法:
- .stop——event.stopPropagation()停止傳播
- .prevent——event.preventDefault()阻止默認(rèn)行為
- .self——if(event.target==event.currentTarget) return 限定觸發(fā)事件的是事件本身,target是事件目標(biāo),currentTarget是當(dāng)前對(duì)象(父級(jí))
- .enter(13)——if(event.keyCode!==13)return
- .ctrl/.alt/.shift/.meta——if(event.ctrlKey) return
<div id="app-01">
<com-keymod >
<com-keymod></com-keymod>
</com-keymod>
</div>
Vue.component('com-keymod',{
render:function(createElement){
var vm=this
return createElement(
'div',
{
on:{
'!click':this.doThisInCapturingMode,
'~mouseover':this.doThisOnceInCapturingModeOver,
'~mouseleave':this.doThisOnceInCapturingModeLeave
}
},
[
createElement('input',{
on:{
keyup:function(event){
if(event.target!==event.currentTarget)
this.value=1
if(!event.shiftKey||event.keyCode!==13)
this.value=2
}
}
}),
vm.value,
this.$slots.default
]
)
},
data:function(){
return {value:0}
},
methods:{
doThisInCapturingMode:function(){
this.value=3
},
doThisOnceInCapturingModeOver:function(){
this.value+=1
},
doThisOnceInCapturingModeLeave:function(){
this.value-=1
}
}
})
var app01=new Vue({
el:'#app-01'
})
- slots
- 靜態(tài)內(nèi)容:this.$slots.default
template:'<div><slot name="foo"></slot></div>'
相當(dāng)于:
render:function(createElement){
return createElement('div',this.$slots.foo)
}
- 作用域slot,子組件
template:'<div><slot :text="msg"></slot></div>'
相當(dāng)于:
render:function(createElement){
return createElement('div',[
this.$scopedSlots.default({
text:this.msg
})])
}
- 作用域slot,父組件
template:'<child><template scope="props"><span>{{props.text}}</span></template></child>'
相當(dāng)于:
render:function(createElement){
return createElement('child',{
scopedSlots:{
default:function(props){
return createElement('span',props.text)
}
}
})
}
學(xué)到此處,我默默回頭復(fù)習(xí)了一下組件內(nèi)slot部分
- JSX
為了把render內(nèi)的語(yǔ)句寫(xiě)的更接近模板一點(diǎn),可以用JSX語(yǔ)法,安裝babel的插件實(shí)現(xiàn)
- 函數(shù)化組件
(存疑)例子有問(wèn)題
- 模板編譯:vue的模板實(shí)際是編譯成了render函數(shù)
####自定義指令
- 第一個(gè)簡(jiǎn)單例子,同樣需要補(bǔ)充完整:
<input id="app-01" v-focus>111
Vue.directive('focus',{
inserted:function(el){
el.focus()
}
})
var app01=new Vue({
el:'#app-01',
//寫(xiě)在實(shí)例作用域內(nèi)
/*
directives:{
focus:{
inserted:function(el){
el.focus()
}
}
}
*/
})
類似于組件的寫(xiě)法,兩種寫(xiě)法,一種是全局自定義指令,另一種是定義在實(shí)例作用域內(nèi)部
- 鉤子函數(shù)和函數(shù)參數(shù)
<div id="app-02" v-demo:hello.a.b="message"></div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello"
},
directives:{
demo:{
bind:function(el,binding,vnode){
var s=JSON.stringify
el.innerHTML=
'name:'+s(binding.name)+'
'+
'value:'+s(binding.value)+'
'+
'expression:'+s(binding.expression)+'
'+
'argument:'+s(binding.arg)+'
'+
'modifiers:'+s(binding.modifiers)+'
'+
'vnode keys:'+Object.keys(vnode).join(',')
}
}
}
})
鉤子函數(shù)有:
- bind:只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用
- inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用(父節(jié)點(diǎn)存在即可調(diào)用)
- update:被綁定元素所在的模板更新時(shí)調(diào)用,而不論綁定值是否變化。
- componentUpdated:被綁定元素所在模板完成一次更新周期時(shí)調(diào)用
- unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用
鉤子函數(shù)參數(shù)有:
- el:指令所綁定的元素
- binding:一個(gè)對(duì)象,包含以下屬性:
- name:指令名(不含v-前綴)
- value:指令的綁定值(計(jì)算后的)
- expression:綁定值的字符串形式
- oldValue:指令綁定的前一個(gè)值
- arg:傳給指令的參數(shù)
- modifiers:一個(gè)包含修飾符的對(duì)象
- vnode:Vue編譯生成的虛擬節(jié)點(diǎn)
- oldValue:上一個(gè)虛擬節(jié)點(diǎn)
- 函數(shù)簡(jiǎn)寫(xiě):
如果只想用bind和update鉤子,可以省略鉤子名稱這一步,直接寫(xiě):
//這是實(shí)例內(nèi)directives內(nèi)
swatch:function(el,binding,vnode){
el.style.backgroundColor=binding.value
}
- 對(duì)象字面量
之前的例子都是傳入對(duì)象名稱,比如:
<div id="app-02" v-swatch="color">111</div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello",
color:'#123456'
},
directives:{
swatch:function(el,binding,v){
el.style.backgroundColor=binding.value
}
}
})
也可以直接傳入一個(gè)js對(duì)象字面量,比如:
<div id="app-02" v-swatch="{color:'#125678'}">111</div>
var app02=new Vue({
el:'#app-02',
data:{
message:"hello"
},
directives:{
swatch:function(el,binding,v){
//注意這里,因?yàn)閭魅氲氖且粋€(gè)對(duì)象,因此需要在value后面加上對(duì)象屬性,才能索引到對(duì)應(yīng)的值
el.style.backgroundColor=binding.value.color
}
}
})
####混合
- 第一個(gè)簡(jiǎn)單例子:
//定義一個(gè)混合對(duì)象
var myMixin = {
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello from mixin!')
}
}
}
var Component = Vue.extend({
mixins:[myMixin]
})
var component=new Component()
>這個(gè)例子中出現(xiàn)了不熟悉的語(yǔ)句,于是我決定去復(fù)習(xí)一下組件的創(chuàng)建和注冊(cè)
- 首先,平常的組件創(chuàng)建,都是直接注冊(cè)的,使用的以下形式
Vue.component("name","arg")
這其實(shí)是把兩個(gè)步驟合為一個(gè)步驟
- 步驟一,創(chuàng)建組件構(gòu)造器
//這是Vue構(gòu)造器的擴(kuò)展,創(chuàng)建了一個(gè)組件構(gòu)造器
var a=Vue.extend(各種參數(shù),比如template)
- 步驟二,注冊(cè)組件
Vue.component("name","組件構(gòu)造器(a)")
所以平時(shí)輸入的arg其實(shí)就是一個(gè)組件構(gòu)造器,在需要復(fù)用組件的組成的時(shí)候,就可以把a(bǔ)rg拆出來(lái)復(fù)用一下
- 創(chuàng)建——注冊(cè)——掛載,組件使用的三步驟:
- 最常見(jiàn):創(chuàng)建和注冊(cè)同時(shí)完成,掛載利用其它vue實(shí)例,在其內(nèi)部使用
<div id="app-01"><mycom></mycom></div>
//創(chuàng)建+注冊(cè)
Vue.component("mycom",{
我是組件構(gòu)造器的內(nèi)容
})
//利用其它vue實(shí)例實(shí)現(xiàn)掛載
var app01=new Vue({
el:'#app-01'
})
- 不注冊(cè)也存在:不注冊(cè)組件,只創(chuàng)建,通過(guò)實(shí)例化,后臺(tái)可以看到console語(yǔ)句
mycom=Vue.extend({
我是組件構(gòu)造器的內(nèi)容
})
new mycom()
>回到開(kāi)始的例子,首先定義了一個(gè)混合對(duì)象myMixinm,然后定義了一個(gè)組件構(gòu)造器,命名為Component,然后用這個(gè)構(gòu)造器創(chuàng)建了一個(gè)實(shí)例,由于沒(méi)有掛載,也沒(méi)注冊(cè)組件,所以只能后臺(tái)看到它確實(shí)存在
- 選項(xiàng)合并和優(yōu)先性:
- 同名鉤子函數(shù)將被合并為一個(gè)數(shù)組,混合對(duì)象的鉤子優(yōu)先調(diào)用
var myMixin = {
template:'<p>2222</p>',
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello from mixin!')
}
}
}
var customcom = Vue.extend({
created:function(){
console.log("hello from component")
},
mixins:[myMixin]
})
new customcom()
//hello from mixin!
//hello from component
- 值為對(duì)象的選項(xiàng),將被合并為同一個(gè)對(duì)象,鍵名沖突時(shí),取組件對(duì)象的鍵值對(duì):
var myMixin = {
methods:{
hello:function(){
console.log('hello from mixin!')
},
foo:function(){
console.log('foo')
}
}
}
var customcom = Vue.extend({
methods:{
hello:function(){
console.log('hello from component!')
},
bar:function(){
console.log('bar')
}
},
mixins:[myMixin]
})
var vm=new customcom()
vm.foo()
vm.bar()
vm.hello()
//foo
//bar
//hello from component!
- 全局注冊(cè)混合對(duì)象:會(huì)影響所有之后創(chuàng)建的Vue實(shí)例,慎用:
Vue.mixin({
template:'<p>222</p>',
created:function(){
var myOption=this.$options.myOption
if(myOption){
console.log(myOption)
}
}
})
var app01=new Vue({
el:'#app-01',
myOption:'hello!'
})
//頁(yè)面:222
//后臺(tái):hello!
- 自定義選項(xiàng)的混合策略:默認(rèn)是覆蓋已有值
var myMixin = {
myOption:"hello from mixin!"
}
var customcom = Vue.extend({
myOption:"hello from vue!",
created:function(){
var myOption=this.$options.myOption
if(myOption){
console.log(myOption)
}
},
mixins:[myMixin]
})
new customcom()
//hello from vue
修改的方式?jīng)]測(cè)試出來(lái)(存疑)
####插件
- vue-element
- vue-touch
- vuex
- vue-router
社區(qū):[awesome-vue](https://github.com/vuejs/awesome-vue#libraries--plugins)
***
接下來(lái)的內(nèi)容需要結(jié)合webpack和vue的插件,進(jìn)階部分到此結(jié)束啦
11111111111
1111111111111111111
111111111111111
1111111111111111
1111111111111111
1111111111111111
1111111111111
1111111111111111
1111111111111111
111111111111111111
111111111111111111
111111111111111
11111111111111111
111111111111111111
11111111111111111
111111111111111111
11111111111111111
11111111111111111
11111111111