模仿B站评论样式
vue.js支持表情输入
个人说说vue组件
好看的评论组件 - undrawui组件库
Talk.vue
<template>
<div style="width: 632px;box-sizing: border-box;margin: 0 auto;">
<talk-item/>
</div>
</template>
<script>
import TalkItem from '@/components/Talk/TalkItem.vue'
export default {
name: 'Talk',
components: {
TalkItem
}
}
</script>
<style>
</style>
TalkItem.vue
<template>
<div class="talk-item-wrapper">
<talk-header/>
<talk-content/>
<talk-img/>
</div>
</template>
<script>
import TalkContent from './TalkContent.vue'
import TalkHeader from './TalkHeader.vue'
import TalkImg from './TalkImg.vue'
export default {
name: 'TalkItem',
components: {
TalkHeader,
TalkContent,
TalkImg
}
}
</script>
<style>
.talk-item-wrapper {
margin-top: 5px;
border: 1px solid #eee;
border-radius: 8px;
background-color: #fff;
box-shadow: 0 3px 6px 0 rgb(0 0 0 / 8%);
}
</style>
TalkContent.vue
<template>
<div class="talk-content-wrapper">
<p>
<span style="font-size:14px;" data-v-488ad047="">Up&Up 的mv <br/>Nothing is impossible with Photoshop</span>
<img src="@/images/se.png" class="emoji-img">
</p>
</div>
</template>
<script>
export default {
name: 'TalkContent',
components: {
}
}
</script>
<style lang="scss">
.talk-content-wrapper {
padding-left: 80px;
margin-bottom: 8px;
.emoji-img {
width: 18px;
width: 18px;
}
}
</style>
TalkHeader.vue
<template>
<div class="talk-header-wrapper">
<div class="avatar-wrapper">
<img src="@/images/avatar.png">
</div>
<div class="user-info-wrapper">
<a href="#" class="nick-name">
zzhua195
</a>
<div class="publish-time">
2020-06-04 17:33
</div>
</div>
<div class="operation">
<i class="iconfont icon-24gf-ellipsisVertical"></i>
</div>
</div>
</template>
<script>
export default {
name: 'TalkHeader',
components: {
}
}
</script>
<style lang="scss">
.talk-header-wrapper {
display: flex;
padding: 10px 10px 0;
margin-bottom: 5px;
position: relative;
.avatar-wrapper {
width: 50px;
height: 50px;
border-radius: 50%;
overflow: hidden;
margin-right: 20px;
img {
width: 100%;
height: 100%;
}
}
.user-info-wrapper {
.nick-name {
display: block;
font-size: 16px;
padding: 6px 0 2px;
}
.publish-time {
font-size: 12px;
color: #9ea2a9;
}
}
.operation {
position: absolute;
right: 6px;
cursor: pointer;
}
}</style>
TalkImg.vue
<template>
<div class="talk-img-wrapper">
<div class="preview-list" v-show="previewShow">
<div v-if="imgUrlList.length == 1" :style="oneImgStyleObj">
<img @click="showSpecifiedImg(0)" :style="oneImgStyleObj" :src="imgUrlList[0]">
</div>
<div v-else :class="['preview-box', `grid-` + imgUrlList.length]">
<img @click="showSpecifiedImg(index)" class="img-item" v-for="(imgUrl, index) in imgUrlList" :src="imgUrl"
:key="index">
</div>
</div>
<div class="watch-list" v-show="watchShow">
<div class="watch-options">
<div @click="watchOption('shouqi')" class="option-item">
<i class="iconfont icon-shouqi"></i>
<span>收起</span>
</div>
<div @click="watchOption('datu')" class="option-item">
<i class="iconfont icon-chakan"></i>
<span>查看大图</span>
</div>
<div @click="watchOption('zuoxuan')" class="option-item">
<i class="iconfont icon-rotate-left"></i>
<span>向左旋转</span>
</div>
<div @click="watchOption('youxuan')" class="option-item">
<i class="iconfont icon-rotate-right"></i>
<span>向右旋转</span>
</div>
</div>
<div class="watch-img" :style="{ height: watchImgHeight }">
<div class="watch-img-prev" @click="changeImg(currWatchImgIndex - 1)"
v-show="imgUrlList.indexOf(currWatchImg) != 0">
<i class="iconfont icon-xiangyou"></i>
</div>
<div class="watch-img-next" @click="changeImg(currWatchImgIndex + 1)"
v-show="imgUrlList.indexOf(currWatchImg) != imgUrlList.length - 1">
<i class="iconfont icon-xiangyou"></i>
</div>
<img id="img1111" :style="currImgStyleObj" ref="currWatchImgRef" :src="currWatchImg">
</div>
<div class="watch-track" v-if="imgUrlList.length > 1">
<div @click="changeImg(index)" :class="['watch-track-item', { 'track-active': currWatchImgIndex == index }]"
v-for="(imgUrl, index) in imgUrlList" :key="index">
<img :src="imgUrl">
</div>
</div>
</div>
<div class="action">
<div class="action-item">
<i class="iconfont icon-zhuanfa"></i>
<span>转发</span>
</div>
<div class="action-item">
<i class="iconfont icon-pinglun"></i>
<span>评论</span>
</div>
<div :class="['action-item', { 'isLike': isLike }]" @click="isLike = !isLike">
<i class="iconfont icon-good"></i>
<span>点赞</span>
</div>
</div>
</div>
</template>
<script>
import img1 from '@/images/1.png'
import img2 from '@/images/2.png'
import img3 from '@/images/3.png'
import img4 from '@/images/4.png'
import img5 from '@/images/5.png'
import img6 from '@/images/6.png'
import test1_100x1600 from '@/images/test1_100x1600.png'
import test1_1600x100 from '@/images/test1_1600x100.png'
import test2_100x800 from '@/images/test2_100x800.png'
import test2_800x100 from '@/images/test2_800x100.png'
import se from '@/images/se.png'
import img7 from '@/images/7.jpg'
import img8 from '@/images/8.jpg'
export default {
name: 'TalkImg',
components: {
},
data() {
return {
imgUrlList: [img1,img2,img3,img4,img5,img6],
previewShow: true,
watchShow: false,
currWatchImg: img1,
currWatchImgIndex: 0,
watchImgHeight: '',
currImgStyleObj: {},
currImgHeight: null,
currImgWidth: null,
currImgRotateValue: 0,
isLike: false,
oneImgStyleObj: { 'border-radius': '4px' }
}
},
mounted() {
if (this.imgUrlList.length == 1) {
let img = new Image()
img.src = this.imgUrlList[0]
img.onload = () => {
if (img.width >= 320) {
this.$set(this.oneImgStyleObj, 'width', '320px')
this.$set(this.oneImgStyleObj, 'height', (320 * img.height / img.width + 'px'))
}
}
}
},
methods: {
showSpecifiedImg(imgIdx) {
let imgUrl = this.imgUrlList[imgIdx]
this.currWatchImgIndex = imgIdx
let image = new Image()
image.src = imgUrl
console.log(image.width, image.height, 343);
image.onload = () => {
this.currImgHeight = image.height
this.currImgWidth = image.width
console.log('onload..............', this.currImgHeight, this.currImgWidth, image.width > 518);
this.resetWatchImg()
if (image.width > 518) {
this.$set(this.currImgStyleObj, 'width', '518px')
this.currImgWidth = 518
this.currImgHeight = 518 * image.height / image.width
console.log(this, this.currImgStyleObj, 'currImgStyleObj');
}
this.currWatchImg = imgUrl
this.previewShow = false
this.watchShow = true
}
},
watchOption(optionName) {
console.log(optionName);
switch (optionName) {
case 'shouqi':
this.previewShow = true;
this.watchShow = false;
this.resetWatchImg()
break;
case 'datu':
this.$prevImg.open(this.currWatchImgIndex, this.imgUrlList)
break;
case 'youxuan':
if (this.currImgRotateValue == 0 || this.currImgRotateValue == 180) {
this.currImgRotateValue += 90
if (this.currImgHeight > 518) {
let newCurrImgHeight = 518
let newCurrImgWidth = this.currImgWidth / this.currImgHeight * 518
this.currImgStyleObj = { width: newCurrImgWidth + 'px', height: newCurrImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
this.watchImgHeight = newCurrImgWidth + 'px'
this.currImgStyleObj.position = "absolute"
this.currImgStyleObj.top = (newCurrImgWidth - newCurrImgHeight) / 2 + 'px'
this.currImgStyleObj.left = (newCurrImgHeight - newCurrImgWidth) / 2 + 'px'
} else {
this.watchImgHeight = this.currImgWidth + 'px'
this.currImgStyleObj = { width: this.currImgWidth + 'px', height: this.currImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
this.currImgStyleObj.position = "absolute"
this.currImgStyleObj.top = (this.currImgWidth - this.currImgHeight) / 2 + 'px'
}
} else if (this.currImgRotateValue == 90 || this.currImgRotateValue == 270) {
this.currImgRotateValue += 90
if (this.currImgRotateValue == 360) {
this.currImgRotateValue = 0
}
this.currImgStyleObj = { width: this.currImgWidth + 'px', height: this.currImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
console.log('currImgStyleObj', this.currImgStyleObj);
this.watchImgHeight = ''
}
break;
case 'zuoxuan':
if (this.currImgRotateValue == 0 || this.currImgRotateValue == 180) {
if (this.currImgRotateValue == 0) {
this.currImgRotateValue = 270
} else {
this.currImgRotateValue -= 90
}
if (this.currImgHeight > 518) {
let newCurrImgHeight = 518
let newCurrImgWidth = this.currImgWidth / this.currImgHeight * 518
this.currImgStyleObj = { width: newCurrImgWidth + 'px', height: newCurrImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
this.watchImgHeight = newCurrImgWidth + 'px'
this.currImgStyleObj.position = "absolute"
this.currImgStyleObj.top = (newCurrImgWidth - newCurrImgHeight) / 2 + 'px'
this.currImgStyleObj.left = (newCurrImgHeight - newCurrImgWidth) / 2 + 'px'
} else {
this.watchImgHeight = this.currImgWidth + 'px'
this.currImgStyleObj = { width: this.currImgWidth + 'px', height: this.currImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
this.currImgStyleObj.position = "absolute"
this.currImgStyleObj.top = (this.currImgWidth - this.currImgHeight) / 2 + 'px'
}
} else if (this.currImgRotateValue == 90 || this.currImgRotateValue == 270) {
this.currImgRotateValue -= 90
this.currImgStyleObj = { width: this.currImgWidth + 'px', height: this.currImgHeight + 'px', transform: `rotate(${this.currImgRotateValue}deg)` }
this.watchImgHeight = ''
}
break;
}
},
changeImg(imgIndex) {
this.showSpecifiedImg(imgIndex)
},
resetWatchImg() {
this.watchImgHeight = ''
this.currImgStyleObj = {}
this.currImgHeight = null
this.currImgWidth = null
this.currImgRotateValue = 0
}
}
}
</script>
<style lang="scss">
.talk-img-wrapper {
padding-left: 80px;
.preview-box {
display: flex;
flex-wrap: wrap;
.img-item {
width: 104px;
height: 104px;
object-fit: cover;
border-radius: 5px;
cursor: pointer;
margin-bottom: 4px;
margin-right: 4px;
display: block;
}
}
.grid-9,
.grid-8,
.grid-7,
.grid-6,
.grid-5,
.grid-3 {
width: 327px;
}
.grid-4,
.grid-2 {
width: 220px;
}
.watch-list {
width: 518px;
color: #666666;
.watch-options {
display: flex;
height: 32px;
background: #f4f5f7;
line-height: 32px;
.option-item {
cursor: pointer;
&:hover {
color: #00a1d6;
}
padding: 0 15px;
i {
margin-right: 5px;
font-size: 14px;
}
span {
font-size: 12px;
}
}
}
.watch-img {
width: 518px;
position: relative;
background-color: #f4f5f7;
overflow: hidden;
transition: all 0.2s;
img {
display: block;
}
.watch-img-prev:hover i {
display: block;
}
.watch-img-next:hover i {
display: block;
}
.watch-img-prev,
.watch-img-next {
z-index: 99999;
cursor: pointer;
position: absolute;
width: 30%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 24px;
color: #fff;
opacity: 0.75;
display: none;
}
}
.watch-img-prev {
left: 0;
transform: rotate(180deg);
}
.watch-img-next {
right: 0;
}
}
.watch-track {
width: 518px;
overflow-x: auto;
overflow-y: hidden;
display: flex;
flex-wrap: nowrap;
padding-bottom: 2px;
.watch-track-item {
width: 54px;
height: 54px;
margin-top: 5px;
border-radius: 4px;
overflow: hidden;
margin-right: 4px;
cursor: pointer;
flex-shrink: 0;
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: #000;
opacity: 0.3;
}
&.track-active::before {
opacity: 0;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
}
.action {
display: flex;
color: #99a2aa;
.action-item {
width: 98px;
height: 42px;
cursor: pointer;
&:hover {
color: #00a1d6;
}
&.isLike {
color: #00a1d6;
}
i {
margin-right: 5px;
}
i,
span {
line-height: 42px;
font-size: 13px;
}
}
}
}
::-webkit-scrollbar {
width: 10px;
height: 5px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
//background-color: #8c8c8c;
background-color: rgba(0, 0, 0, 0.25);
}
::-webkit-scrollbar-track {
background-color: #f6f6f6;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-track {
border: 0;
}</style>