总结网易的年度娱乐圈画转h5画中画的技术实现。

VisionM

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

前言:花时间学习了网易的年度娱乐圈画转h5的技术实现。有些点比较难懂,故此,做个笔记。如果恰好帮助到你,棒呆

首先我们可以浏览一下这个h5,视觉上它是由一幅画慢慢变小,然后再出现另一幅画,特别之处就是当前画,是下一画中的一个小图,一部分。所以叫他画中画。
image

思路历程:第一眼看到这个效果,我的思路就是,把所有的画起始放大n倍,然后当小图的大小刚好是屏幕宽高的时候,就是我们的起始放大倍数,然后倍数慢慢缩小为1。但是这个面临各种问题,1.无法准确计算放大倍数。2.很难计算图片缩小时该在的位置,3.图片很模糊,后来我想,此时再用小图的大图去覆盖它,就有这种效果。这个方法感觉很怪,扯犊子呢。然后...Stupid,too young too simple 我开始找他的源码

然后我们发现它控制台的源码,发现如下:
屏幕快照 2019-10-23 15.42.10.png

//主要方法
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上。这个圈起来,考试要考,呸。。。

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

也就是说,做这个还需要ui爸爸妈妈们的帮助,需要知道,每张图的尺寸大小,图中的小图的位置,大小。
然后,他们已经帮我们准备好啦

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

说了一堆废话,正式开始实现分析。
我们可以看到这个图,并不是自适应屏幕的,而是设定好了既定的尺寸,750x1206,所以我们设计稿就是2倍,iPhone6的。而除了封面是750x1206,所有的原图都是1875x3015。所以,下方长按按钮距离底部还有点距离。这个目前来说网易也没有适配iphonex的情形。

他画图就画图,那他边画边缩是怎么做到的?
这时候要看源码中有这些个东西

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减小,那就乘一个小数

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

阅读 2.2k
829 声望
14 粉丝
0 条评论
你知道吗?

829 声望
14 粉丝
文章目录
宣传栏