我们大多数人使用在线流媒体服务(如 Netflix)观看我们最喜欢的电影或者节目。这篇文章将重点介绍如何通过使用 vue.js 2 建立一个类似风格的电影流媒体 web**** 交互界面(见上图)。
最终的产品可以去这里找: https://codepen.io/itslit/full/mvvjzr 。
尽管 Bulma 将作为应用的 CSS 框架,但是本文将主要集中在 vue.js 的使用和浏览 CSS 式样,如果你想跟着学,我设置了一个可以作为开始学习的地方,所有自定义组合,初始数据对象和必要的需要通过 CDN 引用的外部库(如 Vue-router 等),都可以从 https://codepen.io/itslit/pen/qmzrev 获得。
App 的基本需求
让我们记下这些基本需求:
我们将创建应用程序,让页脚随时出现,而首页、电影和电影预告片将共享相同的屏幕。
为了简单起见,我们将从一个简单 / 可靠的数据对象(对象)开始,它将作为我们所有组件的主存储器。这个存储对象将拥有我们所需要的所有电影信息,并将集中在克里斯托弗 · 诺兰的令人敬畏的电影。下面是数据对象的一部分:
- const movies = {
- "dunkirk": {
- "id": 'dunkirk'"title": 'Dunkirk',
- "subtitle": 'Dunkirk',
- "description": 'Miraculous evacuation of Allied soldiers from
- Belgium, Britain, Canada, and France, who were cut off and
- surrounded by the German army from the beaches and harbor of
- Dunkirk, France, during the Battle of France in World War II.',
- "largeImgSrc": `url('https://image.tmdb.org/t/p/w780/fudEG1VUWuOqleXv6NwCE
- xK0VLy.jpg')`,
- "smallImgSrc': 'https://image.tmdb.org/t/p/w185/fudEG1VUWuOqleXv6NwCExK0
- VLy.jpg',
- "releaseDate ": 'July 21 2017',
- "duration ": '1hr 46min',
- "genre ": 'Action, Drama, History',
- "trailerPath ": 'https://www.youtube.com/embed/F-eMt3SrfFU',
- "favorite ": false
- },
- "interstellar ": {
- ...
- },
- "the - dark - knight - rises ": {
- ...
- },
- "inception ": {
- ...
- },
- "the - prestige ": {
- ...
- }
- }"
既然我们已经创建了主要的存储对象并理解了我们的所有组件是如何布局的,我们就可以开始构建接口了。
让我们首先创建 Vue 实例。我们将把实例挂载到 DOM 元素 app,并返回全局存储的 movies,作为我们的 html 中访问的实例数据对象的一部分。
- const rootApp = new Vue({
- el: '#app',
- data() {
- return {
- movieChoices: movies
- }
- }
- })
我们现在可以开始处理每个独立的组件了。
页脚部分
让我们从列出数据存储中所有电影的固定页脚部分开始。
- <div id="app">
- <section class="hero is-primary is-medium">
- <div class="hero-foot">
- <div class="columns is-mobile">
- <div v-for="movieChoice in movieChoices" class="column">
- <li class="movie-choice">
- <img :src="`${movieChoice.smallImgSrc}`" class="desktop"/>
- <p class="mobile">{{ movieChoice.subtitle }}</p>
- </li>
- </div>
- </div>
- </div>
- </section>
- </div>
对已在上述粗体代码片段的部分:
随着所有 CSS 样式的渲染,我们的应用程序目前应该像这样:
我们创建了页脚,现在我们的目标是创建一个具有我们的 App 标题和描述的电影介绍组件。
我们已经提到了组件将共享相同的屏幕为即将上映的电影预告片和组件(即用户将能够在我们的 APP 中直接通过点击 Intro -> Movie -> MovieComponent 到达相应的链接)。
这是一个完美的用例添加 vue-router 库。vue-router 是 vue.js 官方路由器,是允许组件深入的集成的可配置的路由器,还可以嵌套 / 视图映射等等。
我们将在 js 文件中进行基本路由设置:
- const Intro = {
- template:
- `<div class="hero-body" style="background: #1e1d1d">
- <div class="container has-text-centered">
- <div class="columns">
- <div class="column is-half is-offset-one-quarter vertical-align">
- <h1 class="home-intro">
- VueFlix
- </h1>
- <p class="home-subintro">Select a movie below from the list of
- critically acclaimed Christopher Nolan films.</p>
- </div>
- </div>
- </div>
- </div>`
- }
- const routes = [
- { path: '/', component: Intro },
- ]
- const router = new VueRouter({
- routes
- })
上面你可以看到我们定义的第一个路由组件 Intro,我们路由这个组件 {path:'/', component: Intro} 和实例化我们的路由 new VueRouter({routes})。
注:通过 Vue 有多种方式定义组件模板。电影介绍和电影详细页的组件使用 ES6 的模板文本,定义模板的多个路径。Anthony Gore 有一篇文章: 7 Ways To Define A Component Template in Vue.js。
我们现在需要注入我们的 router 到 Vue 实例,这使整个 App 的路由和渲染到我们的 DOM<router-view></router-view>。
注入我们的 router 到 Vue 实例:
- const rootApp = new Vue({
- el: '#app',
- router: router,
- data() {
- return {
- movieChoices: movies
- }
- }
- })
在 DOM 中渲染我们的路由组件:
- <div id="app">
- <section class="hero is-primary is-medium">
- <router-view></router-view>
- <div class="hero-foot">
- <div class="columns">
- <div v-for="movieChoice in movieChoices" class="column">
- <li class="movie-choice">
- <img :src="`${movieChoice.smallImgSrc}`" class="desktop"/>
- <p class="mobile">{{ movieChoice.subtitle }}</p>
- </li>
- </div>
- </div>
- </div>
- </section>
- </div>
我们已经成功地创建了我们的第一个根路径:path: '/'来显示我们的 IntroComponent。随着我们添加的所有样式,我们的应用程序应该如下所示:
电影组件(多路由)
我们现在 App 已经完成我们指定的主要路径和我们的页脚部分布局。让我们将路径扩展到显示特定电影所有信息的电影组件。
首先,让我们正确地设置导航。如前所述,我们设置页脚的目的是允许用户在电影之间导航。我们将使用 Vue 的 vue-router 的 router-link 组件去实现导航并提供相应的目标地址。
- <div id="app">
- <section class="hero is-primary is-medium">
- <router-view>
- </router-view>
- <div class="hero-foot">
- <div class="columns">
- <div v-for="movieChoice in movieChoices" class="column">
- <router-link :to="`/${movieChoice.id}`" tag="li" class="movie-choice">
- <img :src="`${movieChoice.smallImgSrc}`" class="desktop" />
- <p class="mobile">
- {{ movieChoice.subtitle }}
- </p>
- </router-link>
- </div>
- </div>
- </div>
- </section>
- </div>
- const routes = [
- { path: '/', component: Intro },
- { path: '/:id', component: Movie }
- ]
- const Movie = {
- template:
- `<div>
- <div class="hero-body">
- <div class="container has-text-centered">
- <div class="columns">
- <div class="column is-half is-offset-one-quarter vertical-align">
- <h1 class="home-intro">
- {{ selectedMovie.title }}
- </h1>
- </div>
- </div>
- </div>
- </div>
- </div>`,
- data () {
- return {
- selectedMovie: movies[this.$route.params.id]
- }
- },
- watch: {
- $route () {
- this.selectMovie()
- }
- },
- methods: {
- selectMovie () {
- this.selectedMovie = movies[this.$route.params.id]
- }
- }
- }
- data() {
- return {
- selectedMovie: movies[this.$route.params.id]
- }
- }
- watch: {
- $route() {
- this.selectMovie()
- }
- },
- methods: {
- selectMovie() {
- this.selectedMovie = movies[this.$route.params.id]
- }
- }
- const Movie = {
- template:
- `<div class="hero-body"
- :style="{ 'background-image': selectedMovie.largeImgSrc }">
- <header class="nav">
- <div class="container">
- <div class="nav-left">
- <a class="nav-item">
- <i class="fa fa-bars" aria-hidden="true"></i>
- </a>
- <router-link to="/" class="nav-item is-active">
- Home
- </router-link>
- <a class="nav-item is-active">
- <span class="tag is-rounded">Films</span>
- </a>
- <a class="nav-item is-active">
- Shows
- </a>
- <a class="nav-item is-active">
- Music
- </a>
- </div>
- <div class="nav-right desktop">
- <span class="nav-item">
- <a class="title">
- VueFlix
- </a>
- </span>
- </div>
- </div>
- </header>
- <div class="container description-container">
- <div class="columns">
- <div class="column is-three-quarters">
- <h1 class="title">{{ selectedMovie.title }}</h1>
- <h4 class="subtitle">
- <p class="subtitle-tag">{{ selectedMovie.duration }}</p>
- <p class="subtitle-tag">{{ selectedMovie.genre }}</p>
- <p class="subtitle-tag">{{ selectedMovie.releaseDate }}</p>
- </h4>
- <p class="description">{{ selectedMovie.description }}</p>
- <div class="links">
- <router-link
- :to="{path: '/' + $route.params.id + '/trailer'}"
- class="button play-button">
- Play <i class="fa fa-play"></i>
- </router-link>
- </div>
- </div>
- </div>
- </div>
- </div>`,
- }
- const MovieTrailer = {
- template: `
- <div class="trailer-body" style="background: #1e1d1d">
- <div class="has-text-centered">
- <div class="columns">
- <div class="column vertical-align">
- <iframe
- allowFullScreen
- frameborder="0"
- height="376"
- :src="trailerUrlPath"
- style="width: 100%; min-width: 536px"
- />
- </div>
- </div>
- </div>
- </div>`,
- data () {
- return {
- trailerUrlPath: movies[this.$route.params.id].trailerPath
- }
- }
- }
- const routes = [
- { path: '/', component: Intro },
- { path: '/:id', component: Movie },
- { path: '/:id/trailer', component: MovieTrailer }
- ]
- .favorite-shadow {
- box-shadow: 0 0 50px 15px rgba(251, 255, 15, 0.25);
- }
- .favorite-check {
- position: absolute;
- right: 5px;
- top: 5px;
- z-index: 1;
- color: #fcff4c;
- @media(max-width: $medium) {
- position: initial;
- display: block;
- }
- }
- const Movie = {
- template:
- `<div :class="[{ 'favorite-shadow': selectedMovie.favorite }, 'hero-body']"
- :style="{ 'background-image': selectedMovie.largeImgSrc }">
- <header class="nav">
- ...
- </header>
- <div class="container description-container">
- ...
- ...
- ...
- <div class="links">
- <router-link
- :to="{path: '/' + $route.params.id + '/trailer'}"
- class="button play-button">
- Play <i class="fa fa-play"></i>
- </router-link>
- <a
- class="button is-link favorites-button"
- @click="addToFavorites">
- <span
- :class="[{ 'hide': selectedMovie.favorite }]">
- Add to
- </span>
- <span
- :class="[{ 'hide': !selectedMovie.favorite }]">
- Remove from
- </span>
- favorites
- <i class="fa fa-plus-square-o"></i>
- </a>
- </div>
- </div>
- </div>`,
- data() {
- ...
- },
- watch: {
- },
- methods: {
- selectMovie() {
- ...
- },
- addToFavorites() {
- movies[this.$route.params.id].favorite =
- !movies[this.$route.params.id].favorite
- }
- }
- <div id="app">
- <section class="hero is-primary is-medium">
- <router-view></router-view>
- <div class="hero-foot">
- <div class="columns is-mobile">
- <div v-for="movieChoice in movieChoices" class="column">
- <router-link :to="`/${movieChoice.id}`"
- tag="li"
- class="movie-choice">
- <i :class="[{ 'fa fa-check-circle favorite-check': movieChoice.favorite }]"></i>
- <img :src="`${movieChoice.smallImgSrc}`" class="desktop"/>
- <p class="mobile movie-title">{{ movieChoice.subtitle }}</p>
- </router-link>
- </div>
- </div>
- </div>
- </section>
- </div>
来源: http://www.jianshu.com/p/48296a87365e