To provide our customers with the best video user experience, we want to provide them amazing accessibility features such as a video player which is fixed when opened but follows the user as they scroll the webpage. These are called floating video players. Floating videos are a common sight whether on YouTube, Facebook, or Twitter. In this tutorial, we explore how we can easily create such a floating video player in a Nuxt.Js app.
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
Nuxt.Js is a Vue.Js framework that boasts of improving developer productivity by being simple and powerful. We will use the create-nuxt-app utility to set up our project. Ensure you have yarn or npm v5.2+/v6.1+ installed. Navigate to your preferred working directory and open the terminal.
yarn create nuxt-app nuxtjs-floating-video-player
# OR
npx create-nuxt-app nuxtjs-floating-video-player
# OR
npm init nuxt-app nuxtjs-floating-video-player
Code language: PHP (php)
This will result in a series of prompts to customize your installation. Here are our recommended defaults:
Project name: nuxtjs-floating-video-player
Programming language: JavaScript
Package manager: Yarn
UI framework: Tailwind CSS
Nuxt.js modules: N/A
Linting tools: N/A
Testing frameworks: None
Rendering mode: Universal (SSR/SSG)
Deployment target: Server (Node.js hosting)
Development tools: N/A
What is your Github username?
<your-github-username>
Version control system: Git
Once the setup is complete, feel free to enter and run the project. Once running, it will be available on http://localhost:3000
cd nuxtjs-floating-video-player
yarn dev
# OR
npm run dev
Code language: PHP (php)
Instead of using the default HTML player, we will be using Cloudinary’s video player. This is because it is bundled with many available customizations and features. Cloudinary is a platform that allows delivering the best media experience to our customers through their comprehensive SDKs and APIs.
We install their video player by adding the CDN files into our project. A content delivery network ensures that files are delivered fast and efficiently to our project. In this case, we will use files hosted by the UNPKG CDN. To add assets globally in our app, we’ll add them in the head
section of the nuxt.config.js
file.
// nuxt.config.js
export default {
head: {
...
link: [
...
{
rel: 'stylesheet',
href: 'https://unpkg.com/cloudinary-video-player@1.5.9/dist/cld-video-player.min.css'
}
],
script: [
{ src: 'https://unpkg.com/cloudinary-core@latest/cloudinary-core-shrinkwrap.min.js' },
{ src: 'https://unpkg.com/cloudinary-video-player@1.5.9/dist/cld-video-player.min.js' },
],
}
};
Code language: JavaScript (javascript)
We can also use Cloudinary to host the videos we will be playing. We will be doing so by creating a folder called nuxtjs-floating-video-player
in our media library. If you do not have an account, feel free to register one here. Add this video file to the nuxtjs-floating-video-player
with the pixels-lisa-9963201
name. This means it will have nuxtjs-floating-video-player/pexels-lisa-9963201
as it’s public_id
. This is what we’ll use to refer to the video in our video player.
We first add a <video>
tag to our pages/index.vue
file like we normally would. We add cld-video-player
to ensure it is styled as a Cloudinary video player and additionally we change the theme using the cld-video-player-skin-dark
class. To ensure we can reference the video player in our code, we add the floating-player
id.
<!-- pages/index.vue -->
<template>
<div>
<video
id="floating-player"
autoplay
class="cld-video-player cld-video-player-skin-dark w-1/2 mx-auto h-96"
>
</video>
</div>
</template>
Code language: HTML, XML (xml)
We initialize the player in the mounted
lifecycle hook as well as configure which video will be played.
// pages/index.vue
<script>
export default {
mounted(){
const cld = cloudinary.Cloudinary.new({ cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME, secure:true });
var player = cld.videoPlayer(
'floating-player',
{
controls:true,
floatingWhenNotVisible: 'left',
}
);
}
}
</script>
Code language: HTML, XML (xml)
In the above code, we reference the NUXT_ENV_CLOUDINARY_CLOUD_NAME
environmental variable. These are variables we store outside our code for various reasons, in this case, code portability. To set your own cloud name, create the .env
file in your project root.
touch .env
Code language: CSS (css)
You may obtain your cloud name in your account dashboard. You may add it to the .env
file:
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<your-cloud-name>
Code language: HTML, XML (xml)
We should now have a functional video player similar to the one visible below.
A challenge we have now is that we cannot scroll the page due to the lack of content. Thus we can’t know if our video player floats or not. To fix it, we will create a components/DummyContent.vue
file and populate it will dummy content.
<!-- components/DummyContent.vue -->
<template>
<div class="leading-10">
<div class="hidden lg:block lg:absolute lg:inset-y-0 lg:h-full lg:w-full">
<div class="relative h-full text-lg max-w-prose mx-auto" aria-hidden="true">
<svg class="absolute top-12 left-full transform translate-x-32" width="404" height="384" fill="none" viewBox="0 0 404 384">
<defs>
<pattern id="74b3fd99-0a6f-4271-bef2-e80eeafdf357" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
</pattern>
</defs>
<rect width="404" height="384" fill="url(#74b3fd99-0a6f-4271-bef2-e80eeafdf357)" />
</svg>
<svg class="absolute top-1/2 right-full transform -translate-y-1/2 -translate-x-32" width="404" height="384" fill="none" viewBox="0 0 404 384">
<defs>
<pattern id="f210dbf6-a58d-4871-961e-36d5016a0f49" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
</pattern>
</defs>
<rect width="404" height="384" fill="url(#f210dbf6-a58d-4871-961e-36d5016a0f49)" />
</svg>
<svg class="absolute bottom-12 left-full transform translate-x-32" width="404" height="384" fill="none" viewBox="0 0 404 384">
<defs>
<pattern id="d3eb07ae-5182-43e6-857d-35c643af9034" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" class="text-gray-200" fill="currentColor" />
</pattern>
</defs>
<rect width="404" height="384" fill="url(#d3eb07ae-5182-43e6-857d-35c643af9034)" />
</svg>
</div>
</div>
<div class="relative px-4 sm:px-6 lg:px-8">
<div class="mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto">
<p>Faucibus commodo massa rhoncus, volutpat. <strong>Dignissim</strong> sed <strong>eget risus enim</strong>. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit. Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. <a href="#">Mattis mauris semper</a> sed amet vitae sed turpis id.</p>
<ul role="list">
<li>Quis elit egestas venenatis mattis dignissim.</li>
<li>Cras cras lobortis vitae vivamus ultricies facilisis tempus.</li>
<li>Orci in sit morbi dignissim metus diam arcu pretium.</li>
</ul>
<p>Quis semper vulputate aliquam venenatis egestas sagittis quisque orci. Donec commodo sit viverra aliquam porttitor ultrices gravida eu. Tincidunt leo, elementum mattis elementum ut nisl, justo, amet, mattis. Nunc purus, diam commodo tincidunt turpis. Amet, duis sed elit interdum dignissim.</p>
<h2>From beginner to expert in 30 days</h2>
<p>Id orci tellus laoreet id ac. Dolor, aenean leo, ac etiam consequat in. Convallis arcu ipsum urna nibh. Pharetra, euismod vitae interdum mauris enim, consequat vulputate nibh. Maecenas pellentesque id sed tellus mauris, ultrices mauris. Tincidunt enim cursus ridiculus mi. Pellentesque nam sed nullam sed diam turpis ipsum eu a sed convallis diam.</p>
<blockquote>
<p>Sagittis scelerisque nulla cursus in enim consectetur quam. Dictum urna sed consectetur neque tristique pellentesque. Blandit amet, sed aenean erat arcu morbi.</p>
</blockquote>
<p>Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit.</p>
<h2 class="m-10 text-center font-bold">Everything you need to get up and running</h2>
<p>Purus morbi dignissim senectus mattis <a href="#">adipiscing</a>. Amet, massa quam varius orci dapibus volutpat cras. In amet eu ridiculus leo sodales cursus tristique. Tincidunt sed tempus ut viverra ridiculus non molestie. Gravida quis fringilla amet eget dui tempor dignissim. Facilisis auctor venenatis varius nunc, congue erat ac. Cras fermentum convallis quam.</p>
<p>Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit.</p>
</div>
<div class="mt-6 prose prose-indigo prose-lg text-gray-500 mx-auto">
<p>Faucibus commodo massa rhoncus, volutpat. <strong>Dignissim</strong> sed <strong>eget risus enim</strong>. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit. Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. <a href="#">Mattis mauris semper</a> sed amet vitae sed turpis id.</p>
<ul role="list">
<li>Quis elit egestas venenatis mattis dignissim.</li>
<li>Cras cras lobortis vitae vivamus ultricies facilisis tempus.</li>
<li>Orci in sit morbi dignissim metus diam arcu pretium.</li>
</ul>
<p>Quis semper vulputate aliquam venenatis egestas sagittis quisque orci. Donec commodo sit viverra aliquam porttitor ultrices gravida eu. Tincidunt leo, elementum mattis elementum ut nisl, justo, amet, mattis. Nunc purus, diam commodo tincidunt turpis. Amet, duis sed elit interdum dignissim.</p>
<h2>From beginner to expert in 30 days</h2>
<p>Id orci tellus laoreet id ac. Dolor, aenean leo, ac etiam consequat in. Convallis arcu ipsum urna nibh. Pharetra, euismod vitae interdum mauris enim, consequat vulputate nibh. Maecenas pellentesque id sed tellus mauris, ultrices mauris. Tincidunt enim cursus ridiculus mi. Pellentesque nam sed nullam sed diam turpis ipsum eu a sed convallis diam.</p>
<blockquote>
<p>Sagittis scelerisque nulla cursus in enim consectetur quam. Dictum urna sed consectetur neque tristique pellentesque. Blandit amet, sed aenean erat arcu morbi.</p>
</blockquote>
<p>Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit.</p>
<h2>Everything you need to get up and running</h2>
<p>Purus morbi dignissim senectus mattis <a href="#">adipiscing</a>. Amet, massa quam varius orci dapibus volutpat cras. In amet eu ridiculus leo sodales cursus tristique. Tincidunt sed tempus ut viverra ridiculus non molestie. Gravida quis fringilla amet eget dui tempor dignissim. Facilisis auctor venenatis varius nunc, congue erat ac. Cras fermentum convallis quam.</p>
<p>Faucibus commodo massa rhoncus, volutpat. Dignissim sed eget risus enim. Mattis mauris semper sed amet vitae sed turpis id. Id dolor praesent donec est. Odio penatibus risus viverra tellus varius sit neque erat velit.</p>
</div>
</div>
</div>
</template>
Code language: HTML, XML (xml)
We will then add this component below our video player.
<!-- pages/index.vue -->
<template>
<div>
...
<div class="w-4/5 mx-auto relative py-16 bg-white overflow-hidden">
<dummy-content />
</div>
</div>
</template>
Code language: HTML, XML (xml)
Don’t forget to import the component for it to resolve:
// pages/index.vue
<script>
import DummyContent from "../components/DummyContent.vue"
export default {
components:{
DummyContent
},
...
}
</script>
Code language: HTML, XML (xml)
We should now be able to scroll with the video player disappearing once it’s out of the viewable space.
To make our video player float as we scroll, we need to add the floatingWhenNotVisible
property to the videoPlayer initialization together with the direction we want our player to float to. In our case, we’ll set the player to float to the left.
// pages/index.vue
<script>
...
mounted(){
const cld = cloudinary.Cloudinary.new({ cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME, secure:true });
var player = cld.videoPlayer(
'floating-player',
{
controls:true,
floatingWhenNotVisible: 'left',
}
);
player.source('nuxtjs-floating-video-player/pexels-lisa-9963201.mp4');
}
}
</script>
Code language: HTML, XML (xml)
Now we should have a floating video player just like the one in the screenshot below.
Feel free to read more about the Cloudinary video player on the official docs.