创新编码

不说话,装高手。

Maintain silence and pretend to be an experta

threejs全景视频

2024-07-23 17:06:35Threejs

使用threeJs来实现一个全景视频,带领大家观看梅赛德斯的科技展览🎆,效果大概如下:

42.gif

在开始之前,您必须先了解threeJs的知识,这里为了节省篇幅,就不做过多的说明,有关threeJs入门的只是各位可以查看以下链接:


下面我们开始进入正题

资源准备

我们进入threeJs github地址下载 three.min.jsOrbitControls.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;
}

编码完成后看到如此界面

43.png

准备3d环境

将本次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();
    }
}

我们来看下当前的效果,如下图所示,我们已经成功制作出全景视频了

44.gif

加入底部控制栏

其实我们上面已经将全景视频制作出来,该操作也同样应用于全景直播。细想,接入视频流,转为贴图贴到几何模型,再添加到场景中,最后渲染出来,配上控制器,这不是一摸一样的效果吗😜

现在我们为了让上面视频操作更加完善,我们加入底部控制按钮,播放/暂停、进度条、全屏、窗口重置大小

折叠代码 复制代码
// 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);

让我们来看看最终效果

45.gif

源码地址

因为github不能上传大于100M的文件(原4K视频580M),所以演示项目中的视频我压缩了4次,把他压缩到了62M,因此画质可能有点糊了,但不影响使用。

有条件的同学可以自己去寻找 360全景视频 或更换在线的视频连接,下面是本次教程的源码和视频资源下载网站: