總結網易的年度娛樂圈畫轉h5畫中畫的技術實現。

屏幕快照 2019-10-23 14.10.46.png

前言:花時間學習了網易的年度娛樂圈畫轉h5的技術實現。有些點比較難懂,故此,作個筆記。若是剛好幫助到你,棒呆

首先咱們能夠瀏覽一下這個h5,視覺上它是由一幅畫慢慢變小,而後再出現另外一幅畫,特別之處就是當前畫,是下一畫中的一個小圖,一部分。因此叫他畫中畫。
imagehtml

思路歷程:第一眼看到這個效果,個人思路就是,把全部的畫起始放大n倍,而後當小圖的大小恰好是屏幕寬高的時候,就是咱們的起始放大倍數,而後倍數慢慢縮小爲1。可是這個面臨各類問題,1.沒法準確計算放大倍數。2.很難計算圖片縮小時該在的位置,3.圖片很模糊,後來我想,此時再用小圖的大圖去覆蓋它,就有這種效果。這個方法感受很怪,扯犢子呢。而後...Stupid,too young too simple 我開始找他的源碼

而後咱們發現它控制檯的源碼,發現以下:
屏幕快照 2019-10-23 15.42.10.pnggit

//主要方法
window.WeixinJSBridge && e.play()//處理微信瀏覽器下的音樂播放
initCanvas  //初始化canvas畫布
preload  //加載圖片
init  //初始化場景
showend  //整個動畫結束的回調函數
touchEvent  //touch事件,控制動畫執行
draw  //關鍵方法,就叫他畫畫吧
drawImgOversize  //關鍵方法,就叫他畫局部吧,這個後面會解釋
drawImgMinisize  //關鍵方法,就叫他畫所有吧,這個後面會解釋
drawImage  //關鍵方法,canvas原生方法

屏幕快照 2019-10-23 14.19.36.png

先拋開整個過程當中的gif動畫實現不說。而後主要來畫畫。看這個drawImage方法,666。因此理解了這個方法以後,咱們的思路要轉變一下,用這個實現效果的方法並非放大縮小,而是對圖片的canvas處理,選取圖片的局部,再放到canvas上。這個圈起來,考試要考,呸。。。github

屏幕快照 2019-10-23 14.09.56.png

也就是說,作這個還須要ui爸爸媽媽們的幫助,須要知道,每張圖的尺寸大小,圖中的小圖的位置,大小。
而後,他們已經幫咱們準備好啦 canvas

屏幕快照 2019-10-23 14.11.12.png

說了一堆廢話,正式開始實現分析。
咱們能夠看到這個圖,並非自適應屏幕的,而是設定好了既定的尺寸,750x1206,因此咱們設計稿就是2倍,iPhone6的。而除了封面是750x1206,全部的原圖都是1875x3015。因此,下方長按按鈕距離底部還有點距離。這個目前來講網易也沒有適配iphonex的情形。api

他畫圖就畫圖,那他邊畫邊縮是怎麼作到的?
這時候要看源碼中有這些個東西瀏覽器

this.radio
this.scale = .985,
this.scaleSlow = .995

起初我甚至以爲這些參數是無關緊要的...
首先touchstart的時候觸發的方法中,有這個requestAnimationFrame,就是傳說中一秒執行60次的猛男api。在requestAnimationFrame中不斷執行draw就會不斷地畫,而後咱們用一個變量radio,不斷減少,而後影響到drawImage的參數,說不定能夠實現呢!(這裏面的關係待會再說)
那爲何scale是0.985呢,那0.211行不行,985給多少錢,我211給雙倍啊.....微信

有這麼一個計算公式,咱們須要radio從1減少,那就乘一個小數iphone

this.radio=this.radio*this.scale
//若是這個計算每秒鐘執行60次,那麼this.radio就會變得更小
this.radio=this.radio*this.scale^60

因此這個scale就是一個減少頻度,頻率是每秒60次,因此0.985的話,大概就是執行3,4s。this.radio就會變成0.0幾。儘可能知足areaW/imgw),這個是最小縮放值了,再小就應該換圖縮了。this.scaleSlow = .995就會更,由於咱們注意到,當圖片縮得差很少的時候,就會慢一點,由於圖邊的文字已經露出來了,讓用戶看清楚些。因此limitMax,limitMin就是拿來幹這個的,啥時候該慢一點縮了。固然,這裏的值網易估計是計算器算了,而我是大概算,薛薇有點撈。函數

因此在源碼的中有這些方法
(省略判斷... )? i.radio = i.scaleSlow * i.radio : i.radio = i.scale * i.radio,

if (省略判斷){
    //若是this.radio<= areaW/imgW 那就該換圖了
    this.index++,
    this.radio = 1
}

而後,對於每個場景咱們都須要畫兩張圖,爲何要兩張,由於局部放大以後太模糊了,就再畫一張完整的圖蓋着這個區域,就ok沒問題。學習

mmp?清晰圖與模糊圖很差理解的本身想一想,看這的描述 https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
兩腳離地了,聰明的智商又從新佔領高地了
//例如,拿前兩張圖來分析,咱們先來畫兩張,其餘剩下的再說是吧。。走兩步,沒毛病,skr
[{
  link: "http://static.ws.126.net/f2e/ent/ent_painting2016/images/1.jpg?1520",//①
  imgW: "750",
  imgH: "1206"
}, {
  link: "http://static.ws.126.net/f2e/ent/ent_painting2016/images/2.jpg?1520",//② 
  imgW: "1875",
  imgH: "3015",
  areaW: "375",
  areaH: "603",
  areaL: "1379",
  areaT: "103",
  limitMax: .3,
  limitMin: .2
}]

而後畫兩張圖,再次聚焦到這個方法

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
//drawImage(①,...)
//drawImage(②,...)

畫封面圖,封面圖,長按以後,是從全屏到變小的那張,他一直很清晰,因此叫他清晰圖吧。
他應該完整的顯示在屏幕中。那sx, sy, sWidth, sHeight簡單。

drawImage(①,0,0,1875,3015,dx, dy, dWidth, dHeight)

他畫在屏幕上的位置和大小應該是怎麼樣的。一開始,固然是整個設定區域啦。也就是

drawImage(①,0,0,1875,3015,0, 0, 750, 1260)

他最小是多小?這個是設計稿說了算,咱們能夠看到②中的四個屬性,areaW,areaH,areaL,areaT,就是描述畫中的小畫的大小和位置,因此最小就是這樣

drawImage(①,0,0,1875,3015,areaL, areaT, areaW, areaH)

可是,在縮小的過程當中呢,變化的是後面四個參數,咱們須要計算的就是後面四個參數

drawImage(①,0,0,1875,3015,距離屏幕左側距離, 距離屏幕頂部距離, 當前圖寬, 當前圖高)

一樣的,另外一張圖呢。

畫下一張圖,長按以後,是從模糊到清晰的那張,因此叫他模糊圖。
他應該選一部分區域顯示在屏幕中。那部分區域是啥?就是清晰圖的小小小版,爲啥模糊?選取那麼小的區域,填充在整個設定的屏幕區域,不模糊見鬼了。模糊沒關係,拿上面那張清晰的完完整整蓋住,就完美了

因此,一開始它怎麼畫?就拿一部分,完整的填充設定的屏幕區域就行

drawImage(②,圖片開始選擇的位置x,圖片開始選擇的位置y,圖片選擇的寬,圖片選擇的高,0, 0, 750, 1260)

最後呢,怎麼畫?整張圖,完整的填充設定的屏幕區域就行

drawImage(②,0,0,1875,3015,0, 0, 750, 1260)

因此咱們須要計算的就是前面四個參數

drawImage(②,距離屏幕左側距離,距離屏幕頂部距離,當前圖寬,當前圖高,0, 0, 720, 1260)

因此,如今有個問題是,我不知道我說的意思同窗們get到沒,由於即便沒有,我也要繼續講了。

剩下的計算問題,涉及到,幾何數學,物理,生物,法學,離散,線性規劃,高斯模糊...

首先咱們來計算,drawImgOversize也就是

drawImage(②,距離屏幕左側距離,距離屏幕頂部距離,當前圖寬,當前圖高,0, 0, 720, 1260)

f0bd9be7-98b4-40da-8ebd-a09e3569d0a6 2.jpg

距離屏幕左側距離咱們記爲Sx,也就是圖片中的那個?號。對於某一時刻,HG(③)的寬度=areaW/this.radio

咱們能夠得出一個公式:

//①=areaL,AB=imgW,LK=areaW,②=①-?
>   ①/(AB-LK)=②/(HG-LK)

>   ①/(AB-LK)=②/(HG-LK)===》①/(AB-LK)=(①-?)/(HG-LK)

最後得出

//距離屏幕左側距離:
areaL-areaL/(imgW-areaW)*(areaW/this.radio-areaW)
//同理距離屏幕頂部距離:
areaT-areaT/(imgH-areaH)*(areaH/this.radio-areaH)
//當前圖寬:
areaW/this.radio
//當前圖高:
areaH/this.radio

而後同理:對於drawImgMinisize,某一時刻HG=750*this.radio

最後完整的計算值

this._drawImgOverSize(
        this.containerImage,
        imgNext.imgW,
        imgNext.imgH,
        imgNext.areaW,
        imgNext.areaH,
        imgNext.areaL,
        imgNext.areaT,
        this.radio,
      )
      this._drawImgMinSize(
        this.innerImage,
        imgCur.imgW,
        imgCur.imgH,
        imgNext.imgW,
        imgNext.imgH,
        imgNext.areaW,
        imgNext.areaH,
        imgNext.areaL,
        imgNext.areaT,
        this.radio,
      )

_drawImgOverSize (i, iw, ih, aw, ah, al, at, r) {
      this.ctx.drawImage(
        i,
        al - (aw / r - aw) * (al / (iw - aw)),
        at - (ah / r - ah) * (at / (ih - ah)),
        aw / r,
        ah / r,
        0,
        0,
        750,
        1206,
      );
    }

  _drawImgMinSize (i, ciw, cih, iw, ih, aw, ah, al, at, r) {
      this.ctx.drawImage(
        i,
        0,
        0,
        ciw,
        cih,
        750 * (1 - r) * (al / (iw - aw)),//與下面是同樣的值
        // ((ah / r - ah) * (at / (ih - ah)) * r * 1206) / ah,//網易的以爲太過算式複雜
        1206 * (1 - r) * (at / (ih - ah)),
        750 * r,
        1206 * r,
      );
    }

ok,就這樣吧....

github 地址

在線預覽demo
github

相關文章
相關標籤/搜索