创意视频分屏 - HTML5 & JS

和朋友聊天时无意间发现的一个创意视频分屏的想法, 来自beoplay这个高端耳机的宣传页面, 在自己实现的过程中, 也发现了视频自适应居中的这个设计细节, 自己也捣鼓了出来, 话说在之后产品的宣传主页视频可以一试的, 从两个视角去诠释同一个故事, 就是对这样有心思的设计细节没有任何抵抗力呢。

目标

仿造beoplay这款耳机主页的宣传视频来实现一个类似的视频分屏的效果。

版本1.0(不加视频自适应居中)

right video是静止的不动的, 同时位于最底层。 目前所有的伸缩都是在控制left video, 同时由于我们设置了left video的z-index为3, 那么左边的视频会覆盖在右边的视频上。接下来的目标就很明确了, 我们需要追踪鼠标在整个container里面的位置(我们会用占宽度的百分比来表示), 然后, 通过改变左边视频的宽度, 同时也就把位于下面的右边视频暴露出来了, 来达到切换的效果。

理解到这一步之后,目标就是如何改变视频宽度了。

如何改变视频宽度

这里其实隐藏着一个陷阱, 就是因为我们不可以直接改变视频本身的宽度,因为视频的高宽比在拍摄的时候就决定了, 我们如果只拉长视频而不同时提高高度的话, 就会使得在纵轴上部分内容被遮盖到, 大致意思如图:

而这并不是我们想要的效果, 我们希望, 视频可以维持在同一个大小,而改变的只是视窗的横向移动
这一步我用到了一个trick, 就是overflow: hidden;, 我们在左边的视频外面包住了一层div, 因为我们控制的就是左边的视频, 然后我们来改变这个“wrapper”的宽度(同时保证保证里面的left video的宽度占比仍然占最外面container的100%), 来改变我们的视窗, 所以这个wrapper在网页上所包住的部分, 加上了overflow: hidden;遮去了超出部分后, 显示的就是左边的视频了。

比如一开始, 我们希望两个视频都各占一半, 那么左边的视窗一开始就会是占比原container的宽度的50%, 而原视频本身的宽度应该不变, 即container的宽度的100%, 所以我们需要给left video赋上200%的宽度, 理由是这个属性是对其直接父级的div起作用的。

引入视窗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* clipper 即左侧视频的视窗 */
#clipper {
width: 50%;
position: absolute;
top: 0;
bottom: 0;
overflow: hidden;
}

/* 该属性作用于左侧视频 */
#clipper video {
width: 200%;
position: absolute;
z-index: 10;
}

效果如下:

之后的事情就清楚了, 我们只需要改变左边视窗的宽度, 并同时保证视频本身相对原容器的宽度不变。写的js代码如下:

1
2
3
4
5
6
7
8
9
var rect = videoContainer.getBoundingClientRect(),
offset = e.pageX - videoContainer.offsetLeft,
position = ((e.pageX - videoContainer.offsetLeft) / videoContainer.offsetWidth) * 100;

if (position <= 100) {
videoClipper.style.width = position+"%";
leftVideo.style.width = ((100/position)*100)+"%";
leftVideo.style.zIndex = 3;

简单地说, 只需保证videoClipper.style.width * leftVideo.style.width = 100%即可。

那最后一个改变z-index的目的, 就是确保左边的视频一直叠加在右边的视频之上。

版本2.0(增加了自适应居中)

初衷: 因为从拍摄的角度, 大部分的时间, 我们都会把拍摄的主体放在视频的中间位置, 而如果我们只是简单地移动视窗而不改变视窗的主角位置的话, 我们在鼠标移动的时候, 就只能看到边边角的视频内容了, 这违反设计直觉。

因此我们希望在移动视窗的同时, 同时移动视频本身的位置来弥补视窗的偏差。相应的js代码是:

1
2
3
4
5
6
offset = e.pageX - videoContainer.offsetLeft,
offsetRight = videoContainer.offsetWidth - offset,

// for adaptive resizing:
rightVideo.style.webkitTransform = "translate(" + offset / 2 + "px, 0)";
leftVideo.style.webkitTransform = "translate(-" + offsetRight / 2 + "px, 0)";

画外音: 使用transform来改变DOM元素的位置相比直接改写他们的”定位属性“有很多好处, 其中最突出的就是, transform使用的是GPU, 而那些top\left等的定位元素使用的是CPU, 我们希望充分利用GPU和它提供的硬件加速, 同时transform也不会触发网页的repaint, 从而在渲染上更加的smooth和fast。

那这个地方,offset就是鼠标在container里面距离左边边框的距离, 那么我们同时根据这个距离, 让左边的视频在视窗移动时, 往相反的方向以一半的速度移动来弥补视窗偏差。使得可以在视窗移动的时候, 始终保持视频本身拍摄的主体视角也处于该视窗的主体视角! 原理可以参考这个图:

那么也就大功告成啦!

总结

HTML5对media文件更多功能上的支持使得我们可以更好的操作media文件, 包括视频的开始暂停, 以及各种音量的调节, 而这个程序实例, 是一个很好的对相关API的使用的一个示范和一个小小的创意细节。这个项目, 也是饭后和朋友聊天聊出来的实现方案,也希望看到更多的创意脑洞和美的设计!

这个是这个项目的github repo, 这个是项目的展示demo 欢迎评论和pull request~

Q&A

后续的一些疑问和解答:(基于V2ex和微博的评论)

这个链接中间的 2K-4K 对比差不多一个意思?
A: 哈哈在分屏效果上是类似的, 不过那个对图片的处理就会简单很多啦, 感觉就是在某个位置以左叠加一个高斯模糊的滤镜就可以了~ 相比之下, 对视频的处理还要考虑到视窗和视频本身的移动呢! 不过谢谢分享啦!

多个声音怎么办?两个声音会重叠嘛?
A: 我做了音轨的渐变处理, 也就是根据视频的相对大小来相对改变音量, 也就是说, 当某个视频占主体位置的时候, 另一个视频的声音是会渐弱的, 这样可能使用体验更好吧! 在博文最后有 demo 的网址, 你可以试试, 我 host 在了 github pages 上, 对了, 这个 demo 只在桌面端 work , 在手机端还是实现不了这个效果呢, 有点可惜啦, 如果有改进的建议的话, 希望能提出来啦~

快速拉扯的时候边缘缝隙比较大?
A: 啊对的!我调试的时候也发现是有, 原因也很明显, 我只对整个 container 绑定了 mousemove 事件, 而 html 页面其他元素是不会监听 mousemove 的, 因此当鼠标快速移出 container 的时候, 就会发现边缘缝隙较大了, 我现在就改改 code , 加个 condition , 如果鼠标在 container 之外, 就把视频宽度订死为全屏~谢谢啦!

一起加油!