老規矩,直接看圖!
效果如下:
高清大圖!
碼農多年,老眼昏花,動圖看不清楚? !那就看靜態截圖! ! !
不同分數效果如下:
看完了賣家秀,我們來看看產品的製作過程吧!
canvas繪製圓環1、vue中,<template lang=pug>裡的程式碼如下:
canvas#baseCanvas是底部的灰色圓環
canvas#myCanvas是上邊的彩色圓環
需要用css樣式幫助我們把彩色圓環蓋到灰色圓環上邊。
2、css樣式: 3、js-canvas的樣式繪製程式碼這段程式碼也很簡單,看canvas的api即可
3-1、vue元件中,script標籤頂部定義所需的變數
3-2、vue的methos物件中,定義方法三個:
drawBaseCanvas:用來繪製底部灰色圓環。由於灰色圓環沒有動畫效果,所以一開始就繪製一個完整的灰色圓環即可。 drawClrCanvas:用來繪製上方邊緣的彩色圓環。 clearCanvas:用來清空畫布。這是彩色圓環動畫需要。因為我們圓環動畫效果的核心就是,每隔一段時間就把彩色圓環清空一下,然後把結束角度值增大、重畫,這樣連續起來就是動畫。
以下是三個方法的程式碼:
上邊三個方法裡邊的程式碼,幾乎都是canvas API的應用,看教學即可。
只有在draoClrCanvas方法中,canvas圓形的繪製時,arc的參數裡關於開始值、結束值的設定。
起始值決定了圓環的起始繪製位置,結束值決定了結束的位置(我好像說了一句廢話,但是冥思苦想後的思想描述文字,不想刪除哈哈哈)
這個結束值的計算,對我來說還是比較麻煩的。
count變數為什麼要這麼計算,我也忘了我是怎麼鼓搗出來的了。
this.grade是100以內的正整數,表示分數。被定義在data中,預設是0分。
所以一開始彩色圓環就看不見,因為起始點和結束點都是0點。
如果改變grade的值,從0-100,canvas彩色圓環的值也會改變。
這樣,只要我們逐漸修改grade的值,重新繪製,彩色圓環就會逐漸遞增,實現動畫效果。
圓環動畫效果由於我這裡需求特殊,需要使用者每次翻到canvas所在swiper時,才會觸發動畫(後來更麻煩一點需要柱狀圖和canvas部分有個入場效果後,動畫才開始。效果就是上圖中最長的那張gif動畫那樣)。
所以我得借助swiper才能實現。在swiper切換的回呼函數中,從0開始不停遞增grade分數,並重新觸發彩色圓環的繪製,進而達到動畫效果。
vue中我用的swiper是'vue-awesome-swiper'。她的用法我在其他文章中寫過步驟。
swiper在vue-data中的配置裡,有一個on物件。在on物件中的slideChange函數,就是每次翻頁swiper時會觸發的回呼函數。
這裡我說一下幾個比較特別的點:
(1)vm:是我早就在vue的script中儲存的變量,初始化為null,然後在mounted中,將其賦值為vue實例物件。
初始化資料、繪製灰色圓環
透過這個方法,我在vue實例物件- data - swiper - 回呼函數中去拿vue實例物件- data中的grade和gradeTarget屬性值,並對其進行修改。
ps:我也不知道這麼做是不是很傻的一種做法,當時做到這裡時是我遇到的一個難題,不知道怎麼在swiper的on回調中獲取vue實例。於是就有了這麼曲線救國的方法。如果看官有更好的解決方案,希望可以提供我一個新的思路,感激不盡哦親
(2)(this.activeIndex == 2 && vm.isStar) || (this.activeIndex == 1 && !vm.isStar)
這裡是因為業務,才這麼判斷,可以忽略。
this在swiperChange函數中指向swiper物件。 this.activeIndex是swiper實例的屬性,用官方的話說傳回當前活動塊(激活塊)的索引。可以理解他指的是目前翻到的是哪一頁,就是目前你所看的swiper-slide的下標。
我因為使用者的身份,會判斷性的決定目前canvas所在swiper前一頁是否展示。 如果不展示就根本不會繪製前一頁,那麼相應的當前頁的swiper的下標就會變成(index-1)。
總而言之,當滿足條件、使用者翻到canvas所在swiper頁面後,我要觸發if裡邊的圓環繪製邏輯。否則就走到else裡初始化資料頁面的狀態、清除定時器暫停動畫、並把彩色圓環清空
(3)vm.aniShow
在我上篇《純css繪製長條圖》裡邊說了,長條圖的動畫要跟canvas的動畫一起說。因為他們的動畫實作需要配合swiper的切換。說的就是這裡的程式碼:
vue - data - aniShow屬性變成true時,div.row就會加入ani這個class類別名稱:
同樣,aniShow為true,progress的高度就會附上自己的目標值,也就是這個progress的實際高度經過百分制轉換後被賦予給了style屬性的height。
此時,因為progress的transition監聽了height變化,就開始有了高度漸增的長條圖遞增動畫了。
而ani類別名下,progress的transition-delay實現了其高度錯開遞增效果。
可能只看文字描述很晦澀,再看一眼效果:
(4)彩色圓環繪製程式碼部分
gradeTarget是實際分值,是最終要繪製到的結果。
grade從0開始,自增到gradeTarget的大小。
這裡我沒有直接++vm.grade,我也不知道自己當時咋想的。
if判斷,如果grade遞增到了目標值gradeTarget或大於目標值,就停止遞增,並讓grade=gradeTarget。屬於臨界值的判斷。在運動功能中,又算碰撞偵測。
反之,不到目標的話,就清除上一次繪製的canvas畫布,在grade遞增變化後重新繪製新的彩色圓環。
(5)所有這些放到setTimeout中,暫停500毫秒再執行,是為了等柱圖和環圖入場後,在開始繪製圓環的遞增效果。
其實上邊程式碼都是很簡單的邏輯處理,看官們讀一遍程式碼應該就差不離了。
新想法:
這個效果是我很久以前做的,今天在整理製作方法的時候,我想到自己程式碼的一種最佳化方案:
其實沒必要在定時器裡重新呼叫彩色圓環繪製方法。我們直接改的是this.grade屬性,監聽這個屬性的改變就好了其實。這樣此屬性在定時器中被修改,圓環方法就會自動執行。
這還是一個想法,還需要我的實踐。
中間文字的遞增效果:因為grade是每次遞增的分數,所以利用vue的雙向資料綁定,直接把grade當作分數值綁定到對應dom視圖處。
最後,圓環和上邊長條圖的動畫結合,就是animation控制一下動畫延遲即可。很簡單的。
index.vue原始碼:
(註,原始碼稍作整理,單獨提取。為了完整性也為了保護其他業務代碼,部分變數名稱做了修改,可能會和之前截圖中略微不同)
<template lang='pug'> .indexs#Indexs.app-bg transition(name=fade) swiper#swiperBox(:options=swiperOption ref=mySwiper) swiper-slide.swiper-slide1 .container .up swiper-slper-slide1 .container .up swiper-slper-slide. -slide2(v-if=isShow) .my-shark .up swiper-slide.swiper-slide3 .container .data-cont .data.data01 .data01-charts .row(v-for='item,index in Data' :key=index :class='aniShow ? ani:' ) .data-txt {{item.grade > 0 ? item.grade : '無資料'}} .progress(:class='item.grade == 0 ? nodata : ' :style='height: ' + (aniShow ? (item.grade >= 100 ? (100 * 1.5) / 100 : item.grade == 0 ? 0.04 : item.grade * 1.5 / 100) : 0) +'rem') span.pg-data .week {{item.week}} .data.data02 .data02-charts .canvas-box //- baseCanvas canvas#baseCanvas.my-canvas(ref=baseCanvas width=174 height=174) // - canvas canvas#myCanvas.my-canvas.clr-canvas(ref=myCanvas width=174 height=174) .canvas-data #[span.num {{grade}}]分</template><script>var vm = null, timer1 = null, /* canvas基礎值*/ c = null, //document .getElementById(myCanvas); ctx = null, //canvas-2d畫布x = 161 / 2 + 1, //圓心座標r = (161 - 10) / 2; //半徑大小/* swiper元件*/import { swiper, swiperSlide } from vue-awesome-swiper;import { getData } from ../io/getData;export default { name: Indexs, components: { swiper, swiperSlide }, data() { return { grade: 0, //圓環圖分數gradeTarget: 78.54, //實際得分數,可ajax請求資料後修改isShow: true,//是否展示第二頁swiper aniShow: false,//是否開啟柱圖動畫Data:[{ week : 第一週, grade: 0 }, { week: 第二週, grade: 30 }, { week: 第三週, grade: 99.99 }, { week: 第四週, grade: 76.98 }, { week: 第五週, grade: 100 }], swiperOption: { //swiper參數notNextTick: true, direction: vertical, grabCursor: true, setWrapperSize: true, autoHeight: true, slHeightView: : false, mousewheelControl: false, height: window.innerHeight, // 高度設置,佔滿設備高度resistanceRatio: 0, observeParents: true, initialSlide: 2 - 1, //設置初始化時,swiper的預設展示頁面,從零開始on: { slideChange() { if ( (this.activeIndex == 2 && vm.isShow) || (this.activeIndex == 1 && !vm.isShow) ) { console.log(this.activeIndex, vm.isShow, 繪製動畫); setTimeout(function() { // 配合展示柱狀圖動畫vm.aniShow = true; // 定時器不斷觸發定時器不斷觸發繪製彩色圓環,實作圓環動畫效果timer1 = setInterval(function() { // 中間分數文案變更var num = vm.grade; num++; if (num >= vm.gradeTarget) { vm.grade = vm.gradeTarget; clearInterval(timer1); } else { vm.grade = num; } vm.clearCanvas(); vm.drawClrCanvas(); }, 1000 / 60); }, 500); } else { //翻頁後,初始化資料頁的狀態、清除定時器暫停動畫、並把彩色圓環清空console.log(其他頁); clearInterval(timer1); vm.grade = 0; vm.aniShow = false; vm.clearCanvas (); } } } } }; }, computed: {}, mounted() { // 初始化資料、繪製灰色圓環vm = this; c = this.$refs.myCanvas; ctx = c.getContext(2d); this.drawBaseCanvas(); }, methods: { drawBaseCanvas() { // canvas繪製/* 基礎值*/ var c = this.$refs.baseCanvas , //document.getElementById(myCanvas); // debugger; ctx = c.getContext(2d), o = x, randius = r; /* 預設灰色圓圈*/ ctx.strokeStyle = #eee; ctx.lineWidth = 10; ctx.beginPath(); ctx.arc(o, o, randius, 0, 2 * Math.PI); ctx.arc(o, o, randius, 0, 2 * Math.PI); ctx .stroke(); }, clearCanvas() { // 清除畫布ctx.clearRect(0, 0, 200, 200); }, drawClrCanvas() { var gradient = ctx.createLinearGradient(75, 50, 5, 90); gradient.addColorStop(0, #C88EFF); gradient.addorStop(1.0, #7EpV. = gradient; //用漸變進行填充ctx.lineWidth = 10; ctx.lineCap = round; ctx.shadowColor = rgba(191,142,255, 0.36); ctx.shadowBlur = 8; ctx.shadowOffsetY = 8; ctx.shadowBlur = 8; ctx.shadowOffsetY = 8; grade / (100 / 2) + 1; ctx.arc(x, x, r, Math.PI, Math.PI * count, false); ctx.stroke(); } }};</script><style lang='scss'>// 柱圖.row { position: relative; z-index: 1; width: 0.61rem; margin-bottom: -0.28 - 0.08 - 0.38rem; text-align: center;}.data-txt { font-size: 0.2rem; line-height: 0.2rem; margin-bottom: 0.09rem;}.progress { height: 0rem; transition: height 0.5s ease -in-out;}.ani { @for $i from 1 to 6 { &:nth-of-type(#{$i}) { .progress { transition-delay: #{$i * 0.15}s; } } } // &:nth-of-type(1) { // . progress { // transition-delay: .4s; // } // } // &:nth-of-type(2) { // .progress { // transition-delay: .8s; // } // } // &:nth-of-type(3) { // .progress { // transition-delay: 1s; // } // } // &:nth-of-type(4) { // .progress { // transition-delay: 1.4s; // } // } // &:nth-of-type(5) { // .progress { // transition-delay: 1.8s; // } // }}.pg-data { display: block; width: 0.12rem; height: 100%; margin: 0 auto; background: linear-gradient(0deg, #c88eff 0%, #7e5cff 100%); box-shadow: 0 -0.04rem 0.14rem 0 rgba(129, 93, 255, 0.4); border-radius: 0.05rem 0.05rem 0 0;}// 0分顯示規則.nodata { .pg-data { border-radius: 0; background: #e7e7e7; box-shadow: nonebox-shadow: nonebox-shadow: nonebox-shadow: none; }}.week { font-size: 0.2rem; line-height: 0.2rem; margin-top: 0.08rem; color: #666;}// 環圖- data02資料部分.data02-charts { margin-top: 0.32rem; height: 1.61rem;}.canvas-box { position: relative ; float: left; width: 1.61rem; height: 1.61rem; margin-left: 0.92rem;}.my-canvas { width: 1.61rem; height: 1.61rem;}.clr-canvas { position: absolute; top: 0; left: 0;}.canvas-data v. position: absolute; top: 0.56rem; left: 0; right: 0; margin: auto; margin-left: -0.1rem; text-align: center; font-size: 0.24rem; .num { font-size: 0.32rem; font-weight: 600; }}</style >
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。