自定义视频播放控件样式
In this guide, you will learn how to create a custom video player using the <video>
element and CanJS. The custom video player will:
在本指南中,您将学习如何使用<video>
元素和CanJS创建自定义视频播放器。 自定义视频播放器将:
- Have custom play and pause buttons. 具有自定义播放和暂停按钮。
- Show the current time and duration of the video. 显示视频的当前时间和时长。
Have a
<input type="range">
slider that can adjust the position of the video.有一个
<input type="range">
滑块可以调整视频的位置。
The final player looks like:
最终玩家如下所示:
The following sections are broken down into the following parts:
以下各节分为以下几部分:
The problem — A description of what the section is trying to accomplish.
问题 -对本节要完成的工作的描述。
What you need to know — Browser or CanJS APIs that are useful for solving the problem.
您需要知道的 -对解决问题有用的浏览器或CanJS API。
The solution — The solution to the problem.
解决方案 —解决问题的方法。
建立 (Setup)
START THIS TUTORIAL BY Forking THE FOLLOWING CodePen:
通过分叉以下CodePen开始本教程 :
Click the
Edit in CodePen
button. The CodePen will open in a new window. Click theFork
button.单击
Edit in CodePen
按钮。 CodePen将在新窗口中打开。 单击Fork
按钮。
This CodePen:
该CodePen:
Creates a
<video>
element that loads a video. Right click and select “Show controls” to see the video’s controls.创建一个
<video>
元素来加载视频。 右键单击并选择“显示控件”以查看视频的控件 。Loads CanJS's custom element library: Component.
加载CanJS的自定义元素库: Component 。
问题 (The problem)
In this section, we will:
在本节中,我们将:
Create a custom
<video-player>
element that takes asrc
attribute and creates a<video>
element within itself. We should be able to create the video like:创建一个具有
src
属性的自定义<video-player>
元素,并在其内部创建一个<video>
元素。 我们应该能够像这样创建视频:<video-player src:raw="http://bit.ly/can-tom-n-jerry"> </video-player>
The embedded
<video>
element should have the native controls enabled.嵌入的
<video>
元素应启用本机控件。
When complete, the result will look exactly the same as the player when you started. The only difference is that we will be using a custom <video-player>
element in the HTML
tab instead of the native <video>
element.
完成后,结果将与开始时的播放器完全相同。 唯一的区别是,我们将在HTML
选项卡中使用自定义的<video-player>
元素,而不是本机的<video>
元素。
你需要知道的 (What you need to know)
To set up a basic CanJS application (or widget), you define a custom element in JavaScript and use the custom element in your page’s HTML
.
要设置基本的CanJS应用程序(或小部件),请在JavaScript中定义一个自定义元素,然后在页面的HTML
使用该自定义元素。
To define a custom element, extend Component with a tag that matches the name of your custom element. For example:
要定义自定义元素,请使用与您的自定义元素名称匹配的标签扩展Component 。 例如:
Component.extend({
tag: "video-player"
})
Then you can use this tag in your HTML page:
然后,您可以在HTML页面中使用此标记:
<video-player></video-player>
But this doesn’t do anything ... yet. Components add their own HTML through their view property:
但这还没有任何作用...。 组件通过其view属性添加自己HTML:
Component.extend({
tag: "video-player",
view: `<h2>I am a player!</h2>`
});
A component’s view is rendered with its ViewModel. For example, we can make a <video>
display "http://bit.ly/can-tom-n-jerry"
by defining a src
property on the ViewModel
and using it in the view like:
组件的视图使用其ViewModel呈现。 例如,我们可以通过在ViewModel
上定义src
属性并在如下视图中使用它来使<video>
显示"http://bit.ly/can-tom-n-jerry"
:
Component.extend({
tag: "video-player",
view: `
<video>
<source src="{{src}}"/>
</video>
`,
ViewModel: {
src: {type: "string", default: "http://bit.ly/can-tom-n-jerry"}
}
});
But we want the <video-player>
to take a src
attribute value itself and use that for the <source>
’s src
. For example, if we wanted the video to play "http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
instead of "http://bit.ly/can-tom-n-jerry"
, we would:
但是我们希望<video-player>
本身采用src
属性值,并将其用于<source>
的src
。 例如,如果我们希望视频播放"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
而不是"http://bit.ly/can-tom-n-jerry"
,那么我们将:
Update
<video-player>
to pass"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
with toChild:raw:更新
<video-player>
以通过toChild:raw传递"http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"
:<video-player src:raw="http://dl3.webmfiles.org/big-buck-bunny_trailer.webm"/>
Update the ViewModel to define a
src
property like:更新ViewModel以定义
src
属性,例如:Component.extend({ tag: "video-player", view: ` <video> <source src="{{src}}"/> {{!}} </video> `, ViewModel: { src: "string" } });
Finally, to have a <video>
element show the native controls, add a controls
attribute like:
最后,要使<video>
元素显示本机控件,请添加如下controls
属性:
<video controls>
解决方案 (The solution)
Update the JS tab to:
将JS标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({ //
tag: "video-player", //
view: ` {{!}}
<video controls> {{!}}
<source src="{{src}}"/> {{!}}
</video> {{!}}
`, //
ViewModel: { //
src: "string", //
} //
}); //
Update the HTML to:
将HTML更新为:
<video-player src:raw="http://bit.ly/can-tom-n-jerry"></video-player> <!---->
在播放和暂停视频时更改播放/暂停按钮 (Make play / pause button change as video is played and paused)
问题 (The problem)
When the video is played or paused using the native controls, we want to change the content of a <button>
to say “Play” or “Pause”.
使用本机控件播放或暂停视频时,我们希望将<button>
的内容更改为“播放”或“暂停” 。
When the video is played, the button should say “Pause”. When the video is paused, the button should say “Play”.
播放视频时,按钮应显示“暂停” 。 视频暂停时,按钮应显示“播放” 。
We want the button to be within a <div>
after the video element like:
我们希望按钮位于视频元素之后的<div>
内,例如:
</video>
<div>
<button>Play</button>
</div>
你需要知道的 (What you need to know)
To change the HTML content of the page, use {{#if(expression)}} and {{else}} like:
要更改页面HTML内容,请使用{{#if(expression)}}和{{else}},如下所示:
<button>{{#if(playing)}} Pause {{else}} Play {{/if}}</button>
The view responds to values in the ViewModel. To create a
boolean
value in the ViewModel do:该视图响应ViewModel中的值。 要在ViewModel中创建
boolean
值,请执行以下操作:ViewModel: { // ... playing: "boolean", }
Methods can be used to change the ViewModel. The following might create methods that change the
playing
value:方法可用于更改ViewModel 。 以下可能会创建更改
playing
价值的方法:ViewModel: { // ... play() { this.playing = true; }, pause() { this.playing = false; }, }
You can listen to events on the DOM with on:event. For example, the following might listen to a click on a
<div>
and calldoSomething()
:您可以使用on:event监听DOM 上的事件 。 例如,以下内容可能会监听
<div>
的点击并调用doSomething()
:<div on:click="doSomething()">
<video>
elements have a variety of useful events, including play and pause events that are emitted when the video is played or paused.
解决方案 (The solution)
Update the JavaScript tab to:
将JavaScript标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({
tag: "video-player",
view: `
<video controls
on:play="play()" {{!}}
on:pause="pause()"> {{!}}
<source src="{{src}}"/>
</video>
<div> {{!}}
<button> {{!}}
{{#if(playing)}} Pause {{else}} Play {{/if}} {{!}}
</button> {{!}}
</div> {{!}}
`,
ViewModel: {
src: "string",
playing: "boolean", //
play() { //
this.playing = true; //
}, //
pause() { //
this.playing = false; //
}, //
}
});
使单击“播放/暂停”按钮播放或暂停视频 (Make clicking the play/pause button play or pause the video)
问题 (The problem)
When the play/pause <button>
we created in the previous section is clicked, we want to either play or pause the video.
当单击上一节中创建的播放/暂停 <button>
,我们要播放或暂停视频。
你需要知道的 (What you need to know)
CanJS prefers to manage the state of your application in ViewModel. The <video>
player has state, such as if the video is playing
. When the play/pause button is clicked, we want to update the state of the ViewModel and have the ViewModel update the state of the video player as a side effect.
CanJS倾向于在ViewModel中管理应用程序的状态。 <video>
播放器具有状态,例如正在playing
视频。 单击“ 播放/暂停”按钮时,我们要更新ViewModel的状态,并让ViewModel更新视频播放器的状态作为副作用。
What this means is that instead of something like:
这意味着它不是这样的:
togglePlay() {
if ( videoElement.paused ) {
videoElement.play()
} else {
videoElement.pause()
}
}
We update the state like:
我们将状态更新为:
togglePlay() {
this.playing = !this.playing;
}
And listen to when playing
changes and update the video
element like:
并在playing
更改时收听并更新video
元素,例如:
viewModel.listenTo("playing", function(event, isPlaying) {
if ( isPlaying ) {
videoElement.play()
} else {
videoElement.pause()
}
})
This means that you need to:
这意味着您需要:
Listen to when the
<button>
is clicked and call a ViewModel method that updates theplaying
state.收听单击
<button>
时的声音,并调用更新playing
状态的ViewModel方法。Listen to when the
playing
state changes and update the state of the<video>
element.收听
playing
状态更改时的playing
并更新<video>
元素的状态。
You already know everything you need to know for step #1. (Have the button call a togglePlay
method with on:click="togglePlay()"
and make the togglePlay()
method toggle the state of the playing
property.)
你已经知道你需要知道的步骤#1的一切。 (让按钮使用on:click="togglePlay()"
调用togglePlay
方法,并使togglePlay()
方法切换playing
属性的状态。)
For step #2, you need to use the connectedCallback lifecycle hook. This hook gives you access to the component’s element and is a good place to do side-effects. Its use looks like this:
对于步骤2 ,您需要使用connectedCallback生命周期挂钩。 该挂钩使您可以访问组件的元素,并且是处理副作用的好地方。 它的用法如下所示:
ViewModel: {
// ...
connectedCallback(element) {
// perform mischief
}
}
connectedCallback
gets called once the component’s element
is in the page. You can use listenTo to listen to changes in the ViewModel’s properties and perform side-effects. The following listens to when playing
changes:
一旦组件的element
在页面中,就会调用connectedCallback
。 您可以使用listenTo来侦听ViewModel的属性中的更改并执行副作用。 playing
更改时,以下内容会监听:
ViewModel: {
// ...
connectedCallback(element) {
this.listenTo("playing", function(event, isPlaying) {
})
}
}
Use querySelector
to get the <video>
element from the <video-player>
like:
使用querySelector
从<video-player>
获取<video>
元素,例如:
element.querySelector("video")
<video>
elements have a .play() and .pause() methods that can start and stop a video.
<video>
元素具有可以启动和停止视频的.play()和.pause()方法。
解决方案 (The solution)
Update the JavaScript tab to:
将JavaScript标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({
tag: "video-player",
view: `
<video controls
on:play="play()"
on:pause="pause()">
<source src="{{src}}"/>
</video>
<div>
<button on:click="togglePlay()"> {{!}}
{{#if(playing)}} Pause {{else}} Play {{/if}}
</button>
</div>
`,
ViewModel: {
src: "string",
playing: "boolean",
play() {
this.playing = true;
},
pause() {
this.playing = false;
},
togglePlay() { //
this.playing = !this.playing; //
}, //
connectedCallback(element) { //
this.listenTo("playing", function(event, isPlaying) { //
if (isPlaying) { //
element.querySelector("video").play(); //
} else { //
element.querySelector("video").pause(); //
} //
}); //
} //
}
});
显示当前时间和持续时间 (Show current time and duration)
问题 (The problem)
Show the current time and duration of the video element. The time and duration should be formatted like: mm:SS
. They should be presented within two spans like:
显示视频元素的当前时间和持续时间。 时间和持续时间的格式应为: mm:SS
。 它们应在两个范围内显示:
</button>
<span>1:22</span>
<span>2:45</span>
你需要知道的 (What you need to know)
Methods can be used to format values in can-stache. For example, you can uppercase values like this:
可以使用方法来格式化can-stache中的值。 例如,您可以像这样大写值:
<span>{{upper(value)}}</span>
With a method like:
使用类似的方法:
ViewModel: { // ... upper(value) { return value.toString().toUpperCase(); } }
The following can be used to format time:
以下内容可用于格式化时间:
formatTime(time) { if (time === null || time === undefined) { return "--"; } const minutes = Math.floor(time / 60); let seconds = Math.floor(time - minutes * 60); if (seconds < 10) { seconds = "0" + seconds; } return minutes + ":" + seconds; }
Time is given as a number. Use the following to create a number property on the ViewModel:
时间以数字形式给出。 使用以下命令在ViewModel上创建一个number属性:
ViewModel: { // ... duration: "number", currentTime: "number" }
<video>
elements emit a loadmetadata event when they know how long the video is. They also emit a timeupdate event when the video’s current play position changes.<video>
元素在知道视频多长时间时会发出loadmetadata事件 。 当视频的当前播放位置更改时,它们还会发出timeupdate事件 。videoElement.duration
reads the duration of a video.videoElement.duration
读取视频的时长。videoElement.currentTime
reads the current play position of a video.videoElement.currentTime
读取视频的当前播放位置。
You can get the element in an stache
on:event
binding with scope.element like:您可以使用scope.element像下面这样
on:event
绑定中获取元素:<video on:timeupdate="updateTimes(scope.element)"/>
解决方案 (The solution)
Update the JavaScript tab to:
将JavaScript标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({
tag: "video-player",
view: `
<video controls
on:play="play()"
on:pause="pause()"
on:timeupdate="updateTimes(scope.element)" {{!}}
on:loadedmetadata="updateTimes(scope.element)"> {{!}}
<source src="{{src}}"/>
</video>
<div>
<button on:click="togglePlay()">
{{#if(playing)}} Pause {{else}} Play {{/if}}
</button>
<span>{{formatTime(currentTime)}}</span> / {{!}}
<span>{{formatTime(duration)}} </span> {{!}}
</div>
`,
ViewModel: {
src: "string",
playing: "boolean",
duration: "number", //
currentTime: "number", //
updateTimes(videoElement) { //
this.currentTime = videoElement.currentTime || 0; //
this.duration = videoElement.duration; //
}, //
formatTime(time) { //
if (time === null || time === undefined) { //
return "--"; //
} //
const minutes = Math.floor(time / 60); //
let seconds = Math.floor(time - minutes * 60); //
if (seconds < 10) { //
seconds = "0" + seconds; //
} //
return minutes + ":" + seconds; //
}, //
play() {
this.playing = true;
},
pause() {
this.playing = false;
},
togglePlay() {
this.playing = !this.playing;
},
connectedCallback(element) {
this.listenTo("playing", function(event, isPlaying) {
if (isPlaying) {
element.querySelector("video").play();
} else {
element.querySelector("video").pause();
}
});
}
}
});
使范围在当前时间显示位置滑块 (Make range show position slider at current time)
问题 (The problem)
Create a <input type="range"/>
element that changes its position as the video playing position changes.
创建一个<input type="range"/>
元素,该元素随视频播放位置的变化而改变其位置。
The <input type="range"/>
element should be after the <button>
and before the currentTime
span like:
<input type="range"/>
元素应位于<button>
和currentTime
跨度之前,例如:
</button>
<input type="range"/>
<span>{{formatTime(currentTime)}}</span> /
你需要知道的 (What you need to know)
The range input can have an initial value, max value, and step size specified like:
范围输入可以指定一个初始值,最大值和步长,例如:
<input type="range" value="0" max="1" step="any"/>
The range will have values from 0 to 1. We will need to translate the currentTime to a number between 0 and 1. We can do this with a computed getter property like:
范围的值从0到1。我们需要将currentTime转换为0到1之间的数字。我们可以使用计算得到的getter属性来完成此操作,例如:
ViewModel: { // ... get percentComplete() { return this.currentTime / this.duration; }, }
Use key:from to update a value from a ViewModel property like:
<input value:from="percentComplete"/>
解决方案 (The solution)
Update the JavaScript tab to:
将JavaScript标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({
tag: "video-player",
view: `
<video controls
on:play="play()"
on:pause="pause()"
on:timeupdate="updateTimes(scope.element)"
on:loadedmetadata="updateTimes(scope.element)">
<source src="{{src}}"/>
</video>
<div>
<button on:click="togglePlay()">
{{#if(playing)}} Pause {{else}} Play {{/if}}
</button>
<input type="range" value="0" max="1" step="any" {{!}}
value:from="percentComplete"/> {{!}}
<span>{{formatTime(currentTime)}}</span> /
<span>{{formatTime(duration)}} </span>
</div>
`,
ViewModel: {
src: "string",
playing: "boolean",
duration: "number",
currentTime: "number",
get percentComplete() { //
return this.currentTime / this.duration; //
}, //
updateTimes(videoElement) {
this.currentTime = videoElement.currentTime || 0;
this.duration = videoElement.duration;
},
formatTime(time) {
if (time === null || time === undefined) {
return "--";
}
const minutes = Math.floor(time / 60);
let seconds = Math.floor(time - minutes * 60);
if (seconds < 10) {
seconds = "0" + seconds;
}
return minutes + ":" + seconds;
},
play() {
this.playing = true;
},
pause() {
this.playing = false;
},
togglePlay() {
this.playing = !this.playing;
},
connectedCallback(element) {
this.listenTo("playing", function(event, isPlaying) {
if (isPlaying) {
element.querySelector("video").play();
} else {
element.querySelector("video").pause();
}
});
}
}
});
使滑动范围更新当前时间 (Make sliding the range update the current time)
问题 (The problem)
In this section we will:
在本节中,我们将:
- Remove the native controls from the video player. We don’t need them anymore! 从视频播放器中删除本机控件。 我们不再需要它们了!
- Make it so when a user moves the range slider, the video position updates. 当用户移动范围滑块时,视频位置会更新。
你需要知道的 (What you need to know)
Similar to when we made the play/pause button play or pause the video, we will want to update the currentTime
property and then listen to when currentTime
changes and update the <video>
element’s currentTime
as a side-effect.
类似于使播放/暂停按钮播放或暂停视频时 ,我们将要更新currentTime
属性,然后在currentTime
变化时进行监听,并更新<video>
元素的currentTime
作为副作用 。
This time, we need to translate the sliders values between 0 and 1 to currentTime
values. We can do this by creating a percentComplete
setter that updates currentTime
like:
这次,我们需要将0到1之间的滑块值转换为currentTime
值。 为此,我们可以创建一个percentComplete
设置器来更新currentTime
例如:
ViewModel: {
// ...
get percentComplete() {
return this.currentTime / this.duration;
},
set percentComplete(newVal) {
this.currentTime = newVal * this.duration;
},
// ...
}
Use key:bind to two-way bind a value to a ViewModel property:
使用key:bind双向绑定一个值到ViewModel属性:
<input value:bind="someViewModelProperty"/>
解决方案 (The solution)
Update the JavaScript tab to:
将JavaScript标签更新为:
import {Component} from "//unpkg.com/can@5/core.mjs";
Component.extend({
tag: "video-player",
view: `
<video {{!}}
on:play="play()"
on:pause="pause()"
on:timeupdate="updateTimes(scope.element)"
on:loadedmetadata="updateTimes(scope.element)">
<source src="{{src}}"/>
</video>
<div>
<button on:click="togglePlay()">
{{#if(playing)}} Pause {{else}} Play {{/if}}
</button>
<input type="range" value="0" max="1" step="any"
value:bind="percentComplete"/> {{!}}
<span>{{formatTime(currentTime)}}</span> /
<span>{{formatTime(duration)}} </span>
</div>
`,
ViewModel: {
src: "string",
playing: "boolean",
duration: "number",
currentTime: "number",
get percentComplete() {
return this.currentTime / this.duration;
},
set percentComplete(newVal) { //
this.currentTime = newVal * this.duration; //
}, //
updateTimes(videoElement) {
this.currentTime = videoElement.currentTime || 0;
this.duration = videoElement.duration;
},
formatTime(time) {
if (time === null || time === undefined) {
return "--";
}
const minutes = Math.floor(time / 60);
let seconds = Math.floor(time - minutes * 60);
if (seconds < 10) {
seconds = "0" + seconds;
}
return minutes + ":" + seconds;
},
play() {
this.playing = true;
},
pause() {
this.playing = false;
},
togglePlay() {
this.playing = !this.playing;
},
connectedCallback(element) {
this.listenTo("playing", function(event, isPlaying) {
if (isPlaying) {
element.querySelector("video").play();
} else {
element.querySelector("video").pause();
}
});
this.listenTo("currentTime", function(event, currentTime) { //
const videoElement = element.querySelector("video"); //
if (currentTime !== videoElement.currentTime) { //
videoElement.currentTime = currentTime; //
} //
}); //
}
}
});
结果 (Result)
When finished, you should see something like the following JS Bin:
完成后,您应该会看到类似以下JS Bin的内容:
自定义视频播放控件样式