netease-news

实现网易新闻的图中图(PIP)《二零一七年娱乐圈画传》查看源码

点击或者扫码预览:

绘画过程剖析

下面的动图,如何实现?直观上感觉像是一个大图,但认真想想必定不对,那得多大的图~看了下网页构造,用了一个 canvas 来显示,应该就是把图像一帧一帧的画到 canvas 下面,比方 1 秒画 60 帧,就能达到顺滑的膨胀成果。

绘制的时候,同时存在两张图,最后的时候图 1 和屏幕重合,图 2 在屏幕外边。随着工夫的推移,图 2 和图 1 一起缓缓放大,最终图 2 和屏幕重合,图 1 膨胀到了图 2 的 PIP 地位。这时候去掉图 1,增加图 3,图 3 和图 2 一起膨胀,而后再增加图 4,始终到最初一张图。

想要实现整个过程,须要保障:

  1. 所有图、屏幕显示区域(宽度最大,长度可能有空白或者滚动条)、以及 PIP 的长宽比是统一的,这样能力在放大后完满重合
  2. 以后的图片,须要晓得下一张图的 PIP 的地位和大小,这样能力放大挪动到指定地位
  3. 个别这种画中画都是有事件程序的,所以要求图片的程序是排列好的,依照程序膨胀

绘画须要用到这个CanvasRenderingContext2D.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)函数,函数的参数比拟多,这里列出来:

参数Description
image绘制到 canvas 的元素,能够是图片、视频和 canvas
sximage 的矩形(裁剪)抉择框的左上角 X 轴坐标
syimage 的矩形(裁剪)抉择框的左上角 Y 轴坐标
sWidthimage 的矩形(裁剪)抉择框的宽度
sHeightimage 的矩形(裁剪)抉择框的高度
dximage 的左上角在指标画布上 X 轴坐标
dyimage 的左上角在指标画布上 Y 轴坐标
dWidthimage 在指标画布上绘制的宽度
dHeightimage 在指标画布上绘制的高度

第一个参数,没啥说的,就是想要绘制的指标元素,咱们这里是图片

sxsy,想要剪裁的图片的左上角坐标,如果是0,0就是从原图的左上角开始

sWidthsHeight,想要剪裁的图片的长宽

以上 4 个参数,互相配合,就能够从 image 上剪出原图的一部分

接下来 dx dy dWidth dHeight是针对画布的参数,也就是说在画布上选一块中央将下面裁剪下来的图像放进去。这时候问题来了,如果裁剪的和画布上筹备放图像的宽高不一样咋办?好办,拉伸或者膨胀

计算公式

下面说到,图片是依照程序一张一张替换和膨胀的,那么怎么晓得我下一张图的 PIP 的地位和大小呢,这须要咱们提前规定好,也就是说我晓得每张图片的大小和 PIP 的大小地位(第一张封面没有)

  {    src: 'cover.jpg',    width: '750',    height: '1206',  },  {    src: 'p1.jpg',    width: '1875',    height: '3015',    pipImg: {      width: '152',      height: '244',      left: '370',      top: '1068',    },  },  {    src: 'p2.jpg',    width: '1875',    height: '3015',    pipImg: {      width: '556',      height: '894',      left: '1251',      top: '1050',    },  },

咱们假如,通过一段时间后,长或者宽放大到原来的 reduce(小于 1),随着 reduce 的缓缓变小,变动也越来越大

以后图片

从封面开始,以后图片此时撑满整个屏幕,随着工夫的推移,逐步放大和挪动,最终放大挪动到下一张图片的 PIP 的大小和地位

这一过程中,整个图片都在画布中,而且是从图片的左上角开始剪裁的,联合下面的drawImage函数的参数,那么sxsysWidthsHeight这 4 个参数就是0,0,以后图片的宽度,以后图片的高度,也就是把整个图片都剪裁下来了,所以只须要计算以后图片在画布中的地位和长宽

察看上图,咱们假如 ABCD 是手机屏幕,也就是以后图片最后的大小和地位

EFGH 以后图片的两头态

HIJK 以后图片的最终大小和地位,也就是下一张图片的 PIP 的大小和地位

做几条辅助线,ES 平行于 HU 平行于 IB,MH 是程度的,RE、TH 是垂直的

所以通过一段时间后,有 EF=AB*reduceEH = AD*reduce

先计算画布中的横坐标dx,最后是 0,随着 reduce 变小,越来越大,最终就是 MH 的长度,也就是 PIP 的 left

ML = AR;AR/AT=AE/AH=AS/AU;所以AR=AT*(AS/AU)而AS=AB-SBAU = AB-HI
AB:以后图片的宽度SB: 两头态宽度,即AB*reduceHI: 下一张图片的PIP的宽度AT: 下一张图片的PIP的left

下面是伪代码,具体的能够看看源码

下一张图片

察看下面的动图,最后 nextImage(下一张图片)的 PIP 占满了画布,而后画布缓缓扩充,最初是整个 nextImage 占满了画布

还是一样的图这里假如:ABCD 是 nextImage

EFGH 看做画布的两头态(扩充过程中),最后和 nextImage(下一张图片)的 PIP 重合,缓缓变大

HIJK 是画布的最后状态,大小就是 nextImage 的 PIP

剖析这个过程,整个过程图像都是撑满了画布的,所以dx,dy,dWidth,dHeight应用固定的值(0,0,屏幕的宽,屏幕的高),只计算剪裁图片的局部即可

先计算剪裁图片的横坐标 sx,最后在 PIP 的左上角,也就是nextImage.pip.left,最终在整张图的左上角(也就是 0),也就是 ML 为剪裁的横坐标

因为 EF 从 HI 缓缓扩充的,变化率是 reduce,所以有 reduce = HI/EF,所以有EF = HI/reduce,这个值随着 reduce 的放大,变得越来越大,最终达到了 AB,就完结了

接下来求 ML

ML = AR;AR/AT=AE/AH=AS/AUAS=AB-EFAU=AB-HI所以ML=AR=AT*AS/AU=AT*(AB-EF)/(AB-HI)AT: nextImage.PIP.leftAB: nextImage.widthEF: nextImage.PIP.width / reduceHI: nextImage.PIP.widthMH: 和AT相等

同理可求sy,sWidth也就是 EF

其余动画

原作的动画很多都是应用背景图片雪碧图+简略动画

这里介绍下 background-positionbackground-size:

background-position:管制背景图片在容器元素中的地位,也就是图片的左上角在元素中的地位

background-size:调整图片到指定大小,百分比绝对于容器元素的尺寸

以封面的"长按"按钮为例:

  1. 要保障在不同宽度的设施上都显示一样的比例的按钮,应用了 vw 作为图片容器的长宽单位
  2. 要保障容器中只显示出按钮,要应用 vw 去设置background-position,这样能够让这个"开始"两字的左上角和容器的左上角重合
  3. 雪碧图的原始大小是固定的,当初想把他适配到不同宽度的设施,须要利用background-size将图片大小调整到绝对于容器的大小,此时须要配合background-position一起调整
width: 15vw;height: 14vw;background: url(./assets/images/sprite_v2.png) no-repeat;background-position: -41.4vw -79.45vw;background-size: 770%;

感激

感激这篇文章的指引