OpenHarmony - ArkUI基于JSAPI實現(xiàn)的360°全景展示
??想了解更多關(guān)于開源的內(nèi)容,請訪問:??
前言
之前在web中實現(xiàn)過該功能,想著直接搬過來修改一下,也能在OpenHarmony上跑起來。其實360度全景展示功能的用途還是挺多的,比如一些購物平臺用于全面展示一件商品,這樣可以更全面直觀的了解這件商品;還有一些售樓平臺,可以去展示一些全景戶型等等。
項目說明
- 工具版本:DevEco Studio 3.0 Beta2。
- SDK版本:3.0.5.2(API Version 7 Beta2)。
- 主要組件:canvas。
效果展示
實現(xiàn)原理
基于canvas畫布,通過繪制一個360°的序列幀圖片實現(xiàn)的。 通過監(jiān)聽手勢滑動來實現(xiàn)圖片幀切換。
實現(xiàn)過程
一、 創(chuàng)建canvas畫布
因為是基于canvas實現(xiàn)的,首先當然要寫個canvas畫布標簽,并添加兩個手勢事件touchstart和touchmove。
<div class="container">
<div class="tt" id="tt"></div>
<canvas
id="canvas"
ref="canvas"
@touchstart="touchStart"
@touchmove="touchMove"
></canvas>
</div>
二、 加載所有序列幀圖片
因為是通過圖片序列幀實現(xiàn)的,所以需要預加載完成所有序列幀圖片才能進行后續(xù)操作,否則會出現(xiàn)在切換圖片的時候還沒加載出來導致空白問題。
下面封裝了一個imgLoad方法來處理所有圖片預加載。
/**
* @param {Array} imgList 需要預加載圖片的數(shù)組
* @param {Function} progressCb 加載進度回調(diào)方法
* @param {Function} completeCb 全部加載完成回調(diào)
*/
const imgLoad = (imgList = [], progressCb, completeCb) => {
let len = imgList.length;
let num = 0;
let progress = 0;
var loadImage = function (src) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
resolve(img); //加載時執(zhí)行resolve函數(shù)
};
img.onerror = function () {
reject('地址錯誤:' + src); //拋出異常時執(zhí)行reject函數(shù)
};
img.src = src;
});
};
function* fn() {
for (let i = 0; i < len; i++) {
yield loadImage(imgList[i]);
}
}
let g = fn();
let value = g.next().value;
resume();
function resume() {
console.log('====')
value.then((img) => {
// 單張加載完成
num++;
progress = parseFloat((100 / len) * num).toFixed(2);
progressCb && progressCb(img, progress);
value = g.next().value;
if (value) {
resume();
} else {
// 全部加載完成
completeCb && completeCb();
}
}).catch((err)=>{
console.log(err)
});
}
}
上面方法有兩個回調(diào)。
- progressCb:單張加載完成回調(diào),該回調(diào)可以監(jiān)聽到加載的進度和獲取到創(chuàng)建的image對象。
- completeCb: 全部加載完成后回調(diào),該回調(diào)成功后可以去做后續(xù)的操作了。
這里有一個比較少見的函數(shù),fn* :生成器函數(shù)(generator function)。
什么是生成器函數(shù)呢?
生成器函數(shù)在執(zhí)行時能夠暫停,后面又能從暫停后繼續(xù)執(zhí)行。 使用yield關(guān)鍵字可以暫停函數(shù)。
調(diào)用一個生成器函數(shù),會得到生成器的迭代器對象。
使用next()方法。被首次(后續(xù))調(diào)用時,其內(nèi)的語句會執(zhí)行到第一個(后續(xù))出現(xiàn)yield的位置為止,yield 后緊跟迭代器要返回的值。
yield*(多了個星號),則表示將執(zhí)行權(quán)移交給另一個生成器函數(shù)(當前生成器暫停執(zhí)行)。
next()-> {value:value1,done:true|false}。
- value:表示本次返回的值,即yield表達式返回的值
- done:表示生成器后續(xù)是否還有yield語句,即生成器函數(shù)是否已經(jīng)執(zhí)行完畢并返回。
注意:next()方法時,如果傳入了參數(shù),那么這個參數(shù)會作為上一條直線的yield語句的返回值
三、調(diào)用圖片預加載方法
- 初始化canvas畫布。
- 創(chuàng)建序列幀圖片路徑數(shù)組。
- 調(diào)用imgLoad方法。
initCanvas(){
let c = this.$refs.canvas;
this.ctx = c.getContext('2d');
let arr = [];
for(var i = 1; i <= this.imgLen; i++){
arr.push(`common/images/car/${i}.png`)
}
imgLoad(arr,(img, progress) => {
lg.log('進度:' + progress);
this.imgList.push(img);
},() => {
lg.log('全部加載完成')
this.cutSpirit();
})
},
四、 監(jiān)聽手勢
touchStart方法監(jiān)聽觸摸開始時手勢,緩存當前觸摸位置的x軸數(shù)據(jù)作為開始數(shù)據(jù)。
touchMove方法是監(jiān)聽滑動中的手勢監(jiān)聽。
- 獲取當前滑動的x軸位置,和緩存的startPoint進行比較。
- unit 是滑動精度單位,當前處理為屏幕寬度除于2倍的序列幀長度360/(64 * 2),就是從x軸為0到屏幕最右邊可以完成2次序列幀旋轉(zhuǎn)。分子越大,滑動越快。
- 每次滑動都需要把當前的位置緩存為startPoint。
- 從屏幕來看,右邊滑動到左邊,x值是減小的,所以定義type為right。
- 從屏幕來看,左邊滑動到右邊,x值是增大的,所以定義type為left。
- 注意:這里定義的type并不是序列幀需要走的type,下一點做詳細講解。
touchStart(e){
let s = e.touches[0].localX;
this.startPoint = s;
// lg.log('觸摸開始:',s);
},
touchMove(e){
let s = e.touches[0].localX;
if((s - this.startPoint) > this.unit){
this.drawImg(this.imgIndex, 'right')
this.startPoint = s;
lg.log('向右:',s)
} else if((s - this.startPoint) < -this.unit){
this.drawImg(this.imgIndex, 'left')
this.startPoint = s;
lg.log('向左:',s)
}
this.startPoint = s;
},
五、繪制序列幀圖片
- 手指滑動的方向不一定是序列幀圖片滑動的方向,還需要看序列幀渲染的順序是順時針還是逆時針。
- 如果序列幀是順時針,type為right是跟序列幀反方向,所以需要逆時針繪制序列幀,所以this.imgIndex–;。
- 如果序列幀是逆時針,type為left是跟序列幀同方向,所以順時針繪制序列幀,所以this.imgIndex++,;。
/**
* @param {Number} n 當前繪制的序列幀下標
* @param {String} type 當前手指滑動的方向
*/
drawImg(n,type){
if(type == "right"){
if(this.imgIndex > 0){
this.imgIndex--;
}else{
this.imgIndex = this.imgLen;
}
}else if(type == "left"){
if(this.imgIndex < this.imgLen){
this.imgIndex++;
}else{
this.imgIndex = this.startIndex;
}
}
this.ctx.clearRect(0,0,this.w,this.h);
this.ctx.drawImage(this.imgList[this.imgIndex],0,0,this.w,this.h);
},
最終效果
代碼地址
https://gitee.com/yango520/ohos-panoramic。
總結(jié)
到這里就基本完成,主要的技術(shù)點在于預加載所有序列幀圖片和監(jiān)聽手勢來繪制序列幀的。