总结网易的年度娱乐圈画转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

相关文章
相关标签/搜索