不说话,装高手。
Maintain silence and pretend to be an experta
使用threeJs来实现一个全景视频,带领大家观看梅赛德斯的科技展览🎆,效果大概如下:
在开始之前,您必须先了解threeJs的知识,这里为了节省篇幅,就不做过多的说明,有关threeJs入门的只是各位可以查看以下链接:
下面我们开始进入正题
我们进入threeJs github地址下载 three.min.js 和 OrbitControls.js,这两个我们等下会用到,其次需要准备一个 360全景视频 ,还有一些小图标之类的。视频下载地址我已经忘了,不过不用担心,上面提到的所有资源我都会放在源码中提供给大家
// html
<div class="big-container">
<div class="screen-container" id="screenContainer">
<div class="video-container" id="videoContainer"></div>
</div>
</div>
// style
* {
margin: 0;
padding: 0;
}
body {
position: relative;
width: 100vw;
height: 100vh;
background-color: #666565;
}
.big-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90vw;
height: 80vh;
}
.screen-container {
width: 100%;
height: 100%;
}
.video-container {
width: 100%;
height: 100%;
background-color: #000000;
}
编码完成后看到如此界面
将本次demo中所需的两个文件引入进来,编写一些初始化函数
<script src="./js/three.min.js"></script>
<script src="./js/OrbitControls.js"></script>
<script>
// 初始化一些参数
var scene = null;
var camera = null;
var renderer = null;
var controls = null;
var video = null;
var mesh = null;
var player = null;
var playVariables = {
playClick: true,
isBigScreen: false,
};
// 获取容器
var videoContainer = document.getElementById("videoContainer");
initScene();
initCamera(videoContainer);
initRenderer(videoContainer);
initControls(videoContainer);
render();
// 初始化场景
function initScene() {
scene = new THREE.Scene();
}
// 初始化相机
function initCamera(element) {
camera = new THREE.PerspectiveCamera(
75,
element.clientWidth / element.clientHeight,
1,
1100
);
camera.position.set(1, 0, 0);
}
// 初始化渲染器
function initRenderer(element) {
renderer = new THREE.WebGLRenderer();
renderer.setSize(element.offsetWidth, element.offsetHeight);
element.appendChild(renderer.domElement);
}
// 初始化控制器
function initControls(element) {
controls = new THREE.OrbitControls(camera, element);
controls.rotateSpeed = 0.05;
controls.enableDamping = true;
controls.dampingFactor = 0.05;
}
// 执行渲染
function render() {
requestAnimationFrame(render);
controls.update();
renderer.render(scene, camera);
}
</script>
执行完上面代码,还是看不到效果的,我们只需要保证不报错即可
initVideo();
initContent();
// 创建video标签并配置一些属性、视频地址等,将video赋值给player方便后面控制用
function initVideo() {
video = document.createElement("video");
video.preload = "auto";
video.crossOrigin = "anonymous";
video.src = "./resources/video.mp4";
player = video;
}
// 创建一个球型几何体并将视频作为纹理贴图贴到材质中,最后生成网格模型并添加到场景里
function initContent() {
var geometry = new THREE.SphereBufferGeometry(300, 90, 90);
geometry.scale(-1, 1, 1);
var texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
var material = new THREE.MeshBasicMaterial({
map: texture,
});
mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
scene.add(mesh);
}
处理完后依旧是漆黑一片,但是可以通过video的autoplay属性让视频播放起来(但是我是用是只在服务器热更新时有效果,可能我没有等待视频加载完成🐶)
在这里我写了一个全屏遮罩
// html
<div class="video-mask">
<img
src="./resources/play.png"
onClick="toggleVideo()"
alt="play.png"
/>
</div>
// style
.video-mask {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
}
.video-mask img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10%;
}
// javascript
// playClick是播放状态 false - 未播放 true - 正在播放
function toggleVideo() {
if (playVariables.playClick) {
document.getElementsByClassName("video-mask")[0].style.display = "none";
playVariables.playClick = false;
player.play();
} else {
playVariables.playClick = true;
player.pause();
}
}
我们来看下当前的效果,如下图所示,我们已经成功制作出全景视频了
其实我们上面已经将全景视频制作出来,该操作也同样应用于全景直播。细想,接入视频流,转为贴图贴到几何模型,再添加到场景中,最后渲染出来,配上控制器,这不是一摸一样的效果吗😜
现在我们为了让上面视频操作更加完善,我们加入底部控制按钮,播放/暂停、进度条、全屏、窗口重置大小
// html
<div class="video-bar">
<div class="video-btn" onclick="toggleVideo()">
<img src="./resources/pause.png" class="btn-hidden" alt="pause.png" width="68%" />
<img src="./resources/play.png" alt="pause.png" width="68%" />
</div>
<div class="progress-container">
<div class="time-box">
<span class="current-video-time">00:00</span>/<span class="total-video-time">00:00</span>
</div>
<div class="progress-wrapper">
<div class="progress" id="progress-play"></div>
</div>
</div>
<div class="full-screen" onclick="toggleBigScreen()">
<img src="./resources/bigscreen.png" alt="bigscreen.png" width="68%" />
</div>
</div>
// 这段style太长了 我就不贴上来了,详细可查看源码
// javascript
// 处理toggleVideo函数
function toggleVideo() {
var imgArr = document.getElementsByClassName("video-btn")[0].children;
if (playVariables.playClick) {
imgArr[1].setAttribute("class", "btn-hidden");
imgArr[0].setAttribute("class", "");
document.getElementsByClassName("video-mask")[0].style.display = "none";
playVariables.playClick = false;
player.play();
} else {
imgArr[0].setAttribute("class", "btn-hidden");
imgArr[1].setAttribute("class", "");
playVariables.playClick = true;
player.pause();
}
}
// 进度条计算以及时间数值
// 在initVideo中添加监听函数
// video可以用ontimeupdate来获取视频总长度和当前播放位置
// s_to_hs函数是秒转时分秒函数,百度可以找到,这里不贴上来,文末源码自取
function initVideo() {
video = document.createElement("video");
video.preload = "auto";
video.crossOrigin = "anonymous";
video.src = "./resources/video.mp4";
player = video;
video.ontimeupdate = function (event) {
var totalTime = s_to_hs(Math.floor(this.duration));
document.getElementsByClassName("total-video-time")[0].innerHTML = totalTime;
var currentTime = s_to_hs(Math.floor(this.currentTime));
document.getElementsByClassName("current-video-time")[0].innerHTML = currentTime;
document.getElementById("progress-play").style.width = `${
(Math.floor(this.currentTime) / Math.floor(this.duration)) * 100
}%`;
// 视频播放完毕时回到最开始的状态
if (currentTime === totalTime) {
video.currentTime = 0;
toggleVideo();
document.getElementsByClassName("video-mask")[0].style.display = "block";
}
};
}
// 点击进度条,视频前进到对应的位置(这边算的可能有些不准,所以操作起来怪怪的)
var progressContainer = document.getElementsByClassName("progress-wrapper")[0];
progressContainer.addEventListener("click", function (event) {
var progressWidth = document.getElementById("screenContainer").offsetWidth - 248;
var percentage = event.offsetX / progressWidth;
video.currentTime = video.duration * percentage;
});
// 全屏处理
function toggleBigScreen() {
if (playVariables.isBigScreen) {
exitFullscreen();
} else {
requestFullScreen();
}
}
// 进入全屏,更改状态
function requestFullScreen() {
var de = document.getElementById("screenContainer");
if (de.requestFullscreen) {
de.requestFullscreen();
} else if (de.mozRequestFullScreen) {
de.mozRequestFullScreen();
} else if (de.webkitRequestFullScreen) {
de.webkitRequestFullScreen();
}
playVariables.isBigScreen = true;
}
// 推出全屏更改状态
function exitFullscreen() {
var de = document;
if (de.exitFullscreen) {
de.exitFullscreen();
} else if (de.mozCancelFullScreen) {
de.mozCancelFullScreen();
} else if (de.webkitCancelFullScreen) {
de.webkitCancelFullScreen();
}
playVariables.isBigScreen = false;
}
// 监听resize,重置场景大小
window.addEventListener("resize", function () {
camera.aspect = videoContainer.clientWidth / videoContainer.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(
videoContainer.offsetWidth,
videoContainer.offsetHeight
);
}, false);
让我们来看看最终效果
因为github不能上传大于100M的文件(原4K视频580M),所以演示项目中的视频我压缩了4次,把他压缩到了62M,因此画质可能有点糊了,但不影响使用。
有条件的同学可以自己去寻找 360全景视频 或更换在线的视频连接,下面是本次教程的源码和视频资源下载网站: