前言
"仙路尽头谁为峰,一见无始道成空。"
2013年初读遮天,十年后遮天的动漫正式上线。依稀记得高中记忆力"三十年河东三十年河西"的萧炎和独断万古的荒天帝。不知道当年经典绝伦的小说,如今改成动漫口碑如何。
于是就打开腾讯视频看看评分,每个视频都要点开才能看到评分和介绍。随即就萌生了用技术整合国漫评分内容的想法。最后历经一周,完成了一个简单的评分展示系统。
上一篇文章写的是腾讯国漫数据获取的部分,这篇文章主要写的是使用vue生态构建前端的部分。
静态展示: 动态展示:
二. 前端设计
前端使用webpack + vue + ElmentPlus + TypeScript + scss,使用vue脚手架创建一个项目,导入到IDE。
页面左侧做一个垂直轮播,右侧显示评分、简介等信息,每次刷新
项目布局
首先使用ElementPlus的container进行布局,将整个页面分为aside和main左右两个区域。
左侧Aside的显示轮播组件<Carousel>,轮播使用的是ElementPlus的carousel组件,直接从官网针贴代码到组件中。
这时候访问前台页面。
从页面看,基本的布局就完成了,接下来就是对轮播优化、main区域展示设计以及css细节优化,先对轮播图进行样式调节。
轮播图
轮播图使用的是ElementPlus的el-carousel走马灯组件。动漫的封面是长图,像素是770 * 1080,这里进行50%的等比例缩放,来设置轮播框的宽高。
下载了一张封面图,通过img标签放在el-carousel-item中,然后进行css设置:
css
$width: 385px;
$height: 540px;
img {
width: $width;
height: $height;
box-shadow: 1px 1px 1px #888888;
border-radius: 16px;
}
.carousel {
bottom: 80px;
}
.el-carousel__item {
width: $width;
height: $height;
padding-left: 50px;
background-color: rgba(0, 0, 0, 0);
}
.el-carousel__mask {
background-color: rgba(0, 0, 0, 0) !important;
}
主要是对img和el-carousel__item组件做了一些细节、定位的设计。这里提一下el-carousel__mask,必须要要加important来强制改变为透明颜色,这样才能和背景色颜色一样。
最后大概是这个样子。
再看看main区域的数据展示。
国漫名称展示
d这一块其实是在后面才设计的,但是布局是在最上方,这里就先说说这里实现。样例如下:
这里没有啥设计,定义了一个title.vue。评分星号是用ElementPlus的el-rate 实现的,标签使用el-tag实现的。
然后就是一些css的微调:
css
.header_div {
width: 510px;
height: 138px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
}
.tags {
color: white;
font-size: 12px;
opacity: 0.6;
margin-left: -4px;
}
.rate-star {
margin: 0px 0 0 20px
}
.mx-1 {
margin: 4px 0 0 7px;
}
.title-name {
color: white;
font-size: 40px;
box-shadow: #141414;
}
评分数据展示
定义了一个score组件,来简单设计了一个评分推荐页面
没有前端设计的这种艺术细胞,所以这里打算复刻腾讯视频的评分展示页。评分数据展示我使用的是ElementPlus的el-card组件,
javascript
<el-card class="box-card">
</el-card>
<style scoped lang="scss">
$background_layout: rgb(236 217 217 / 5%);
.box-card {
width: 326px;
height: 138px;
background: $background_layout;
border: none;
}
</style>
这里只对长宽、颜色进行了简单的定义,el-card先不填充内容。
1. svg图标定义
评分两侧的两个svg是wheat图标,先去icons网站下载wheat svg。
因为想找个一模一样的比较麻烦,我就用Adobe Illustrator做了一个水平镜像。这样就获取了评分两侧的wheat,然后放到@/assets/svg下面。
接着在项目中定义Icon组件,用来引用svg图标。
javascript
setup(props) {
const iconStyle = computed(() => {
const {size, color} = props
let s = `${size.replace('px', '')}px`
return {
fontSize: s,
color: color,
}
})
if (props.name.startsWith('el-icon-')) {
return () => h('el-icon', {
class: 'icon el-icon',
style: iconStyle.value
}, [h(resolveComponent(props.name))])
// svg图标
} else if (props.name.startsWith('svg-icon')) {
return () => h(svg, {name: props.name, size: props.size, color: props.color})
} else {
return () => h('i', {
class: 'icon ' + props.name,
style: iconStyle.value
})
}
}
然后在vue.config.js中使用svg-sprite-loader加载器,将svg文件加载进来,Icon组件就可以引用svg。
javascript
chainWebpack: (config) => {
// 内置的svg处理排除指定目录下的文件
config.module.rule('svg').exclude.add(resolve('src/assets/svg')).end()
config.module
.rule('svg-sprite-loader')
.test(/\.svg$/)
.include.add(resolve('src/assets/svg'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'svg-icon_[name]'
})
}
在原有的svg文件名前加上svg-icon_ 前缀。这样,wheat的svg就可以被Icon组件引用。这一块的具体实现我在之前拆解BuildAdmin的Icon组件和定义svg图标里面都有写,这里不做赘述,有兴趣的话可以参考之前的文章。
这样就能实现完成图标的引用了:
javascript
<Icon class="nav-menu-icon" name="svg-icon_wheat-left" />
<Icon class="nav-menu-icon" name="svg-icon_wheat-right" />
通过Icon组件中name属性,就能使用刚刚加载的的svg图标。
从上图看,两个svg图标是垂直分布,而我想要得的水平分布,所以接下来就利用css进行布局。
2. 评分推荐区域设计
从整体分布来看,因为既有垂直又有水平分布,所以这里要用flex弹性布局,即display:flex。
从左到右,分为评分区域和推荐区域两个div。按照这个思路,这里先将html部分写出来。
html
<template>
<el-card class="box-card">
<div class="user-rating-col__container">
<div class="user-rating-card__left"></div>
<div class="user-rating-card__right"></div>
</div>
</el-card>
</template>
定义了user-rating-card的左右div。left是评分区域,rigth是推荐区域。因为两个区域是水平分布的,这里先将父div设置为弹性分布:
css
.user-rating-col__container {
display: flex;
flex-wrap: nowrap;
}
默认是row水平分布,所以这里可以不定义flex-direction。
评分区域
left评分div从上到下分为垂直分布的三个部分,所以是布局方向flex-direction 是column垂直分布。中间部分是由两个svg,一个评分span构成,使用默认水平分布。
在user-rating-card__left中定义html:
html
<div class="user-rating-card__left">
<span class="user-rating-card__title">腾讯视频评分</span>
<div class="card-wheat">
<Icon class="nav-menu-icon_left" name="svg-icon_wheat-left2" size="38" color="silver"/>
<div class="card-wheat__percent"> 9.7</div>
<Icon class="nav-menu-icon_right" name="svg-icon_wheat-right2" size="38" color="silver"/>
</div>
<span class="user-rating-card__desc">83.4万人点评</span>
</div>
中间的三个部分看做一个整体,都放在一个card-wheat div进行flex布局。
然后先对user-rating-card__left作垂直分布的设置。
css
.user-rating-card__left {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 0 0 auto;
margin-right: 10px;
font-size: 10px;
line-height: 16px;
}
将评分部分设置为column 列分布,居中对齐。再对中间部分card-wheat 做一个水平分布,并使用align-items 和justify-content实现水平和垂直居中分布。
css
.card-wheat {
display: flex;
align-items: center;
justify-content: space-between;
margin: 9px 0 15px;
}
水平分布就不用设置flex-direction,因为默认就是row。最后就是对评分和span文字的css定义:
css
.card-wheat__percent {
display: flex;
justify-content: center;
width: 52px;
font-size: 22px;
line-height: 36px;
font-weight: 500;
color: #ff7612;
white-space: nowrap;
}
span {
color: white;
white-space: nowrap;
}
再看评分区域,已经初具雏形。
里面wheat的svg后来我又给设置成银色了。下面点评的span透明度也设置成了0.6。这个就从后面接着看吧。
弹性布局,贵在弹性二字,这里看着虽然居中,等定义user-rating-card__right区域后再看效果。
推荐区域
先将推荐区域user-rating-card__right进行flex水平布局。
css
.user-rating-card__right {
flex: 1 1 auto;
display: flex;
align-items: center;
}
再对user-rating-card__right布局分析。文字垂直靠右对齐,比例条部分垂直分布,所以分左右两个div水平布局,div内垂直布局。
左侧就是文字lable,右侧就是比例展示,左侧就是一个简单的div:
html
<div class="user-rating-card__labels">
<div class="user-rating-card__label"> 强推</div>
<div class="user-rating-card__label"> 推荐</div>
<div class="user-rating-card__label"> 可看</div>
<div class="user-rating-card__label"> 一般</div>
<div class="user-rating-card__label"> 不推荐</div>
</div>
然后定义css样式:
css
.user-rating-card__labels {
display: flex;
flex-direction: column;
margin-right: 8px;
}
.user-rating-card__label {
flex: 0 0 auto;
display: flex;
justify-content: flex-end;
white-space: nowrap;
height: 16px;
margin: 1px 0;
font-size: 10px;
padding: 0 12px;
color: white;
}
设置flex-direction为垂直对齐,将justify-content 设置为flex-end来尾部对齐,margin后面和比例条一起调整对齐。
右侧比例展示,我使用的是ElementPlus的Progress进度条组件,还可以设置各种颜色。
html
<div class="user-rating-card__bars">
<el-progress v-for="(item, index) in customColors" :percentage="item.percentage"
:color="item.color" :key="index" :show-text="false"/>
</div>
<script setup lang="ts">
import {reactive} from 'vue'
const customColors = reactive([
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100},
])
</script>
在script中定义了颜色和百分比,v-for循环遍历创建了五个el-progress ,其中percentage 组件为显示的百分比,show-text设置为false,这样不显示进度文本。
然后对user-rating-card__bars垂直布局:
css
.user-rating-card__bars {
display: flex;
flex-direction: column;
margin-top: 11px;
}
.el-progress--line {
margin-bottom: 12px;
width: 200px;
}
el-progress--line要设置进度条宽度,margin-top和margin-bottom是为了和左侧文字对齐,自己调出来的。
后面将box-card的width改成了510px,同时对各个组件使用margin进行了微调。最后展示效果:
3. 介绍页设计
定义了一个description组件,展示了动漫简介、title、热度、剧集等基本信息。
这里分成了两个部分,动漫简介其实就是一个div。
动漫简介
这部分的html两三行,没什么好说的。主要实现就是当文本过长是,如何限制住文本,我这里用css设置,最多只显示4行,多余的就用...表示。
css
.description {
width: 510px;
height: 138px;
color: white;
opacity: 0.75;
font-size: 16px;
margin-top: 40px;
// 多行隐藏
max-height: 5rem;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
line-height: 1.2rem;
}
热度、title
同样使用的是el-card卡片进行的展示,左边的图标是从网上下载的svg,右侧是简单的span文本展示,这里看看html。
html
<el-card class="box-card">
<div class="playlist-intro-info__item">
<Icon name="svg-icon_title" size="12"/>
<span class="title">少年不屈,异火不熄!</span>
</div>
<div class="playlist-intro-info__item">
<Icon name="svg-icon_fire" size="12"/>
<span class="hot">11935 · 内地 · 2015 · 虐心爱情 · 东方玄幻</span>
</div>
<div class="playlist-intro-info__item">
<Icon name="svg-icon_time" size="12"/>
<span class="title">共158集,会员看全集</span>
</div>
</el-card>
然后就是定义css样式,对playlist-intro-info__item父div做一个flex弹性分布,用来调整居中。
css
.playlist-intro-info__item {
display: flex;
justify-content: center;
margin-top: 12px;
}
本来我将justify-content设置成了flex-start靠左分布,后来觉得设置成center更有趣些。
接着就是对svg和span进行css定义:
css
span {
color: white;
font-size: 12px;
opacity: 0.6;
margin-left: 8px;
}
svg {
margin-top: 2px;
}
基本上都是对细节和位置的细微调整。至此的页面设计就完成了就下来就是在main区域进行布局排版。最后效果如下:
感觉右边空荡荡的,我直接反手修改布局。设置了左右两个el-aside。
这样就有两个炫酷的轮播框了...
轮播图优化
1. 镜像
但是这时候问题就又来了,左右轮播图是镜像 关系。左侧轮播框指示器在右侧,右侧轮播框的也在右侧,这样就不对称了,调了一阵儿也没成功,后来索性直接使用indicator-position将指示器去掉了。
html
<el-carousel indicator-position="none" />
但是图片的box-shadow 阴影都在右侧,也不是镜像关系,所以我直接复制了一个carousel-right.vue。
然后修改box-shadow,让其在左侧显示阴影。
css
img {
box-shadow: -1px 1px 1px #888888;
}
这样就完成进行两个轮播图的镜像。
2. 轮播同步
这时候两侧轮播图是各玩各的,在el-carousel 有一个属性:pause-on-hover,即鼠标悬浮时暂停自动切换,这个默认值为true。当我悬停在一个轮播框的时候,这个轮播图其实就已经不动了,但另一个还在轮播。所以这里就要想着如何将两个轮播图同步起来。
这时候我就想到了el-carousel的autoplay自动属性了。当我悬停在一个轮播图的时候,就触发一个hover事件,将另一轮播图的autoplay设置为false,这样两个轮播图都不会动了。所以,这里得先定义一个全局状态变量,这里我用的是pinia。
定义状态
定义了一个useCarousel 状态,里面有autoplay属性,初始值为true,自动播放并定义了鼠标进入悬停的mouseEnter和鼠标离开的mouseLeave两个方法。
当鼠标悬停在轮播框,会触发轮播图的pause-on-hover停止播放属性,同时调用mouseEnter,将autoplay设置为false.当鼠标离开,轮播图恢复播放,这时候调用mouseLeave(),将autoplay设置为true。所以两个事件需要绑定在轮播图组件上。
cartoonData变量是为后面存储后台请求预留的字段。
绑定事件
在两个轮播图的el-carousel组件中做以下修改。
html
<el-carousel
:autoplay="carouselStore.state.autoplay"
@mouseenter="carouselStore.mouseEnter"
@mouseleave="carouselStore.mouseLeave"
/>
<script setup lang="ts">
import {useCarousel} from '@/stores/carousel'
const carouselStore = useCarousel()
</script>
el-carousel的autoplay属性由全局状态控制,并用v-on(@)来绑定鼠标悬停和离开事件。通过修改autoplay来通知另一个轮播框是否暂停/恢复播放。
效果如下:
这样,前端部分就涉及完成了,虽然也没什么美感。。接下来就是从后台写一个获取数据的接口,来根据轮播图修改对应的评分等展示信息。
结语
本篇文章主要使用vue、Element Plus构建了前端页面,并对vue生态中常用的技术做了详细的使用场景介绍。通过代码和动图的结合,实现了初版的腾讯国漫评分展示系统。
下一篇文章就是开发后端的API接口,从后台获取国漫数据实时渲染轮播,实时更新国漫介绍、评分等。