Trong bài trước chúng ta đã tìm hiểu về transition và animate trong Vue aps dùng với enter/leave,list thế nhưng để animate và transition cho State (trạng thái), cho dữ liệu thì sao? Cùng theo dõi bài học dưới đây để tìm câu trả lời các bạn nhé!
Animate cho dữ liệu
Xét các ví dụ sau:
- các con số và phép tính
- màu sắc được hiển thị
- vị trí của các node SVG
- kích cỡ và các thuộc tính khác của các phần tử web
Có thể thấy đuộc tất cả những thông tin này đều hoặc đã được lưu trữ sẵn dưới dạng số liệu, hoặc có thể được chuyển đổi thành số liệu. Một khi làm vậy, chúng ta có thể animate những thay đổi trạng thái này bằng cách dùng những thư viện bên thứ ba, kết hợp với hệ thống phản ứng (reactivity) và component của Vue.
Animate cho trạng thái bằng watcher
Watcher cho phép chúng ta animate các thay đổi từ bất kì thuộc tính dạng số nào sang một thuộc tính khác. Điều này nói một cách trừu tượng thì nghe có vẻ phức tạp, vì thế chúng ta hãy xem ví dụ với Greensock sau
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script> <div id="animated-number-demo"> <input v-model.number="number" type="number" step="20"> <p>{
{
animatedNumber}
}
</p> </div>
new Vue({
el: '#animated-number-demo', data:{
number: 0, tweenedNumber: 0}
, computed:{
animatedNumber: function(){
return this.tweenedNumber.toFixed(0);}
}
, watch:{
number: function(newValue){
TweenLite.to(this.$data, 0.5,{
tweenedNumber: newValue}
);}
}
}
)
Kết quả như sau:
Khi bạn cập nhật con số trong input trên đây, thay đổi sẽ được animate. Ví dụ này khá ổn, nhưng nếu dữ liệu không phải là một con số trực tiếp mà là một cái gì đó khác, ví dụ như màu CSS thì sao? Dưới đây là một cách thực hiện điều này khi tích hợp với Tween.js và Color.js:
HTML
<script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4"></script> <script src="https://cdn.jsdelivr.net/npm/color-js@1.0.3"></script> <div id="example-7"> <input v-model="colorQuery" v-on:keyup.enter="updateColor" placeholder="Nhập vào một màu" > <p> <span v-bind:style="{
backgroundColor: tweenedCSSColor}
" class="example-7-color-preview" ></span> </p> <p>{
{
tweenedCSSColor}
}
</p> </div>
VUEJS
var Color = net.brehaut.Color new Vue({
el: '#example-7', data:{
colorQuery: '', color:{
red: 0, green: 0, blue: 0, alpha: 1}
, tweenedColor:{
}
}
, created: function (){
this.tweenedColor = Object.assign({
}
, this.color)}
, watch:{
color: function (){
function animate (){
if (TWEEN.update()){
requestAnimationFrame(animate)}
}
new TWEEN.Tween(this.tweenedColor) .to(this.color, 750) .start() animate()}
}
, computed:{
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 (){
this.color = new Color(this.colorQuery).toRGB() this.colorQuery = ''}
}
}
)
CSS
.example-7-color-preview{
display: inline-block; width: 50px; height: 50px;}
Kiểm tra kết quả như sau:
Transition động cho trạng thái
Cũng giống như component transition của Vue, transition cho dữ liệu cũng có thể được cập nhật trong thời gian thực (real time), và việc này đặc biệt hữu ích khi tạo các prototype (bản thử nghiệm, khuôn mẫu). Ngay cả khi sử dụng một đa giác SVG đơn giản, bạn cũng có thể đạt được nhiều hiệu ứng mà nếu không sử dụng transition bạn có thể sẽ phải tốn kha khá thời gian thử đi thử lại mới tưởng tượng ra được.
Xem ví dụ dưới đây:
Code hoàn thiện của ví dụ trên như sau:
HTML
<div id="app"> <svg width="200" height="200"> <polygon :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>
VUEJS
new Vue({
el: '#app', 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:{
sides: function (newSides, oldSides){
var sidesDifference = newSides - oldSides if (sidesDifference > 0){
for (var i = 1; i <= sidesDifference; i++){
this.stats.push(this.newRandomValue())}
}
else{
var absoluteSidesDifference = Math.abs(sidesDifference) for (var i = 1; i <= absoluteSidesDifference; i++){
this.stats.shift()}
}
}
, stats: function (newStats){
TweenLite.to( this.$data, this.updateInterval / 1000,{
points: generatePoints(newStats)}
)}
, updateInterval: function (){
this.resetInterval()}
}
, 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 var y = -value * 0.9 var angle = Math.PI * 2 / total * index var cos = Math.cos(angle) var sin = Math.sin(angle) var tx = x * cos - y * sin + 100 var 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(' ')}
CSS
svg{
display: block;}
polygon{
fill: #41B883;}
circle{
fill: transparent; stroke: #35495E;}
input[type="range"]{
display: block; width: 100%; margin-bottom: 15px;}
Sắp xếp transtion vào component
Quản lí nhiều transition cho trạng thái có thể làm độ phức tạp của một đối tượng hoặc component Vue tăng lên một cách nhanh chóng. Rất may mắn nhiều animation có thể được trích xuất ra thành các component con chuyên dụng. Chúng ta hãy thử làm chuyện này với ví dụ animate số nguyên ở trên:
HTML
<script src="https://cdn.jsdelivr.net/npm/tween.js@16.3.4"></script> <div id="example-8"> <input v-model.number="firstNumber" type="number" step="20"> + <input v-model.number="secondNumber" type="number" step="20"> ={
{
result}
}
<p> <animated-integer v-bind:value="firstNumber"></animated-integer> + <animated-integer v-bind:value="secondNumber"></animated-integer> = <animated-integer v-bind:value="result"></animated-integer> </p> </div>
VUEJS
// Giờ thì logic tween phức tạp này có thể được dùng lại giữa // hai số nguyên bất kì nào mà chúng ta muốn animte trong ứng dụng. // Component cũng cung cấp một giao diện rõ ràng để thiết lập // thêm nhiều transition động cũng như các kĩ thuật transition // phức tạp. Vue.component('animated-integer',{
template: '<span>{
{
tweeningValue}
}
</span>', props:{
value:{
type: Number, required: true}
}
, data: function (){
return{
tweeningValue: 0}
}
, watch:{
value: function (newValue, oldValue){
this.tween(oldValue, newValue)}
}
, mounted: function (){
this.tween(0, this.value)}
, methods:{
tween: function (startValue, endValue){
var vm = this function animate (){
if (TWEEN.update()){
requestAnimationFrame(animate)}
}
new TWEEN.Tween({
tweeningValue: startValue}
) .to({
tweeningValue: endValue}
, 500) .onUpdate(function (object){
vm.tweeningValue = object.tweeningValue.toFixed(0)}
) .start() animate()}
}
}
) // Đối tượng Vue chính bây giờ không còn gì phức tạp cả! new Vue({
el: '#example-8', data:{
firstNumber: 20, secondNumber: 40}
, computed:{
result: function (){
return this.firstNumber + this.secondNumber}
}
}
)
Kết quả như sau:
Bên trong các component child, chúng ta có thể kết hợp bất kì kĩ thuật transition nào đã được bàn đến với các kĩ thuật được hệ thống transition có sẵn của Vue cung cấp. Với hai công cụ này, thật sự những điều chúng ta không làm được là rất ít. Chúc các bạn thành công!