最近這倆天被臨時拉去寫一個vue消息滾動組件,作為一個后端開發,只會css,js,html,沒有學過vue,最近參考樣例,官網文檔,和開源的滾動消息組件,封裝了一個可以復用的vue組件。直接在html引入該js,定義組件名稱標簽傳遞數據即可。鳴謝github開源項目,本消息滾動組件無需額外引用外部js,無第三方依賴。
2020.0107修改,增加填充父級元素屬性設置
實現源碼如下:
html部分:
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <!--需要添加的屬性配置是每頁顯示的條數 根據li的高度,邊框寬度,li之間的margin來計算--> <div id="app"> <scroll-tony v-bind:data="data" :obj-val="objData"></scroll-tony> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="scroll-tony.js"></script> <script> new Vue({ el: '#app', data: { data: [{ 'label': '無縫滾動第一行無縫滾動第一行第一行無縫滾動第一行第一行無縫滾動第一行第一行無縫滾動第一行', 'unit': '2017-12-16第一行無縫滾動第一行第一行無縫滾動第一行' }, { 'label': '無縫滾動第二行無縫滾動第二行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第三行無縫滾動第三行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第四行無縫滾動第四行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第五行無縫滾動第五行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第六行無縫滾動第六行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第七行無縫滾動第七行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第八行無縫滾動第八行', 'unit': '2017-12-16' }, { 'label': '無縫滾動第九行無縫滾動第九行', 'unit': '2017-12-16' }] }, computed: { objData: function (){ return { rollSpeed: 0.25, //數值越大速度滾動越快(Number) limitMoveNum: 5, //開啟無縫滾動的數據量(Number) hoverStop:true, //是否啟用鼠標hover控制(Boolean) direction:1, //方向 0 往下 1 往上(Number) singleHeight:3, //單步運動幾個信息條后停止(默認值0是無縫不停止的滾動)(Number) rollInterval:2000, //單步停止等待時間(默認值1000ms,設置單步等待時長)(Number) openRoll:true, //是否開啟輪播 默認true(開啟) (Boolean) fwidth:360, fheight:320, // borderWidth:1, //取消默認有整個滾動區域邊框 // borderColor:'#FFB42C', // borderCornerValue:5, liborderWidth:2, //信息條邊框 liborderColor:'#FFFFFF', liborderCornerValue:5, liheight:60, //每條信息展示的高度 backgroudColor:'#E8EDF1',//'#282C34', //信息條背景顏色 labelFontSize:25, //標簽字體大小 labelFontWeight:'bold', labelFontColor:'#282C34', unitFontSize:20, //內容字體大小 unitFontWeight:'normal', unitFontColor:'#1370FB', iconUrl:'img2.png',//'https://www.baidu.com/img/labadong_9f901c0eb1a677ad32efe1d024e12bac.gif', //圖標路徑 iconWidth:50, iconHeight:50, limargin:2, //每個信息條之間的間隔
isAbsolute:false,//當使用false時,開啟填充父級元素,true使用組件默認設置的寬高/自定義設置的寬高
} } } }) </script> </body> </html>
js部分:
Vue.component("scroll-tony",{
template:
`<div ref="wrap" :style="wrapStyle">
<div ref="realBox" :style="pos" @mouseenter="enter" @mouseleave="leave">
<div ref="slotList" style="overflow: 'hidden'">
<solt>
<ul :style="wrapul">
<li ref="wrapli" :style="wrapli" v-for="item in data">
<div class="imgdiv">
<img :style="img" :src='options.iconUrl'/>
</div>
<div :style="content">
<div :style="inner"><span :style="label" v-text="item.label"></span></div>
<div :style="inner"><span :style="unit" v-text="item.unit"></span></div>
</div>
</li>
</ul>
</slot>
</div>
<div v-html="copyHtml" style="overflow: 'hidden'"></div>
</div>
</div>`,
props: {
data: {
type: Array,
default: () => {
return []
}
},
objVal: {
type: Object,
default: () => {
return {}
}
},
},
data () {
return {
xPos: 0,
yPos: 0,
delay: 0,
copyHtml: '',
height: 0,
width: 0, // 外容器寬度
realBoxWidth: 0, // 內容實際寬度
reqFrame: null, // move動畫的animationFrame定時器
singleWaitTime: null, // single 單步滾動的定時器
isHover: false // mouseenter mouseleave 控制this._move()的開關
}
},
created() {
if(this.options.isAbsolute){
this.options.fheight = this.$parent.$el.offsetHeight
this.options.fwidth = this.$parent.$el.offsetHeight;
}
},
computed: {
wrapStyle: {
get (){
//可以根據其他需求作判斷來按需求返回值
return{
height: this.options.fheight+`px`,
width: this.options.fwidth+`px`,
overflow: `hidden`,
border: this.options.borderWidth+`px solid`,
borderRadius:this.options.borderCornerValue+`px`,
borderColor:this.options.borderColor+``
}
}
},
wrapul: {
get(){
return{
listStyle: "none",
padding: "0",
margin: "0 auto"
}
}
},
wrapli:{
get(){
return{
justifyContent:"center",
alignItems:"Center",
height: this.options.liheight+"px",
margin: this.options.limargin+"px",
lineHeight: (this.options.liheight/2)+"px",
border: this.options.liborderWidth+`px solid`,
borderRadius:this.options.liborderCornerValue+`px`,
borderColor:this.options.liborderColor,
display: "flex",
verticalAlign: "middle",
backgroundColor: this.options.backgroudColor
}
}
},
img:{
get(){
return{
float: "left",
width: this.options.iconWidth+"px",
height: this.options.iconHeight+"px",
marginLeft: (this.options.limargin*2)+"px",
marginRight: (this.options.limargin*2)+"px",
}
}
},
content:{
get(){
return{
overflow: "hidden",
width: (this.options.fwidth*0.72)+"px",
height: this.options.liheight+"px"
}
}
},
inner:{
get(){
return{
overflow: "hidden",
width: (this.options.fwidth*0.72)+"px",
height: (this.options.liheight/2)+"px",
}
}
},
label:{
get(){
return{
fontSize: this.options.labelFontSize+"px",
fontWeight: this.options.labelFontWeight+"",
color:this.options.labelFontColor+""
}
}
},
unit:{
get(){
return{
fontSize: this.options.unitFontSize+"px",
fontWeight: this.options.unitFontWeight+"",
color:this.options.unitFontColor+""
}
}
},
/* 滾動實現 */
pos () {
return {
transform: `translate(${this.xPos}px,${this.yPos}px)`, //x,y 軸坐標變化
transition: `all ${this.ease || 'ease-in'} ${this.delay}ms`, //動畫過渡
overflow: 'hidden'
}
},
//一些默認屬性,可通過外部傳遞變量更改
defaultOption () {
return {
rollSpeed: 2, //速度調節數字越大速度越快
limitMoveNum: 5, //啟動無縫滾動最小數據數
hoverStop: true, //是否啟用鼠標hover控制
direction: 1, // 0 往下 1 往上
singleHeight: 0, //單條數據高度有值hoverStop關閉
rollInterval: 1000, //單步停止等待時間
openRoll: true,
switchDelay: 400,
fwidth:360,
fheight:320,
//取消整個滾動區域的邊框
// borderWidth:1,
// borderColor:'#FFB42C',
// borderCornerValue:5,
liborderWidth:1, //信息條邊框
liborderColor:'#FFB42C',
liborderCornerValue:5,
liheight:60, //每條信息展示的高度
backgroudColor:' #7B68EE', //信息條背景顏色
labelFontSize:15, //標簽字體大小
labelFontWeight:'bold',
labelFontColor:'red',
unitFontSize:14, //內容字體大小
unitFontWeight:'bold',
unitFontColor:'gray',
// iconUrl:'img.png', //圖標路徑
iconWidth:50,
iconHeight:50,
limargin:2 //每個信息條之間的間隔
}
},
options () { //我傳遞進來的值
return this.copyObj({}, this.defaultOption, this.objVal)
},
openRoll () { //控制能否自動輪播
return this.options.openRoll
},
scrollSwitch () {
return this.data.length >= this.options.limitMoveNum
},
hoverStopSwitch () {
return this.options.hoverStop && this.openRoll && this.scrollSwitch
},
realSingleStopHeight () {
//傳遞進來的信息條的數目*每個信息條的高度
return (this.options.singleHeight)*(this.options.liheight+this.options.limargin+(this.options.liborderWidth)*2)
},
rollSpeed () {
let singleStep
let rollSpeed = this.options.rollSpeed
singleStep = this.realSingleStopHeight
if (singleStep > 0 && singleStep % rollSpeed > 0) {
console.error('如果設置了單步滾動,rollSpeed需是單步大小的約數,否則無法保證單步滾動結束的位置是否准確')
}
return rollSpeed
}
},
methods: {
_typeof(){
return typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
},
copyObj() {
if (!Array.isArray) {
Array.isArray = function (arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
var name = void 0,
options = void 0,
src = void 0,
copy = void 0,
copyIsArray = void 0,
clone = void 0,
i = 1,
target = arguments[0] || {},
// 使用||運算符,排除隱式強制類型轉換為false的數據類型
deep = false,
len = arguments.length;
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
i++;
}
if ((typeof target === 'undefined' ? 'undefined' : this._typeof(target)) !== 'object' && typeof target !== 'function') {
target = {};
}
// 如果arguments.length === 1 或typeof arguments[0] === 'boolean',且存在arguments[1],則直接返回target對象
if (i === len) {
return target;
}
for (; i < len; i++) {
//所以如果源對象中數據類型為Undefined或Null那么就會跳過本次循環,接着循環下一個源對象
if ((options = arguments[i]) != null) {
// 如果遇到源對象的數據類型為Boolean, Number for in循環會被跳過,不執行for in循環// src用於判斷target對象是否存在name屬性
for (name in options) {
// src用於判斷target對象是否存在name屬性
src = target[name];
// 需要復制的屬性當前源對象的name屬性
copy = options[name];
// 判斷copy是否是數組
copyIsArray = Array.isArray(copy);
// 如果是深復制且copy是一個對象或數組則需要遞歸直到copy成為一個基本數據類型為止
if (deep && copy && ((typeof copy === 'undefined' ? 'undefined' : this._typeof(copy)) === 'object' || copyIsArray)) {
if (copyIsArray) {
copyIsArray = false;
// 如果目標對象存在name屬性且是一個數組
// 則使用目標對象的name屬性,否則重新創建一個數組,用於復制
clone = src && Array.isArray(src) ? src : [];
} else {
// 如果目標對象存在name屬性且是一個對象則使用目標對象的name屬性,否則重新創建一個對象,用於復制
clone = src && (typeof src === 'undefined' ? 'undefined' : this._typeof(src)) === 'object' ? src : {};
}
// 深復制,所以遞歸調用copyObject函數
// 返回值為target對象,即clone對象
// copy是一個源對象
target[name] = copyObj(deep, clone, copy);
} else if (copy !== undefined) {
// 淺復制,直接復制到target對象上
target[name] = copy;
}
}
}
}
return target;
},
arrayEqual(arr1, arr2) {
if (arr1 === arr2) return true;
if (arr1.length !== arr2.length) return false;
for (var i = 0; i < arr1.length; ++i) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
},
_cancle () {
cancelAnimationFrame(this.reqFrame || '')
},
//鼠標移入停止滾動
enter () {
if (this.hoverStopSwitch) {
this.isHover = true //關閉_move
// 防止頻頻hover進出單步滾動,導致定時器亂掉
if (this.singleWaitTime) clearTimeout(this.singleWaitTime)
this._cancle()
}
},
//鼠標離開開始滾動
leave () {
if (this.hoverStopSwitch) {
this.isHover = false //開啟_move
this._move()
}
},
_move () {
// 鼠標移入時攔截_move()
if (this.isHover) return
this._cancle() //進入move立即先清除動畫 防止頻繁touchMove導致多動畫同時進行
this.reqFrame = requestAnimationFrame(
function () {
const h = this.realBoxHeight / 2 //實際高度
const w = this.realBoxWidth / 2 //寬度
let { direction, rollInterval} = this.options
let { rollSpeed } = this
if (direction === 1) { // 上
if (Math.abs(this.yPos) >= h) {
this.$emit('ScrollEnd')
this.yPos = 0
}
this.yPos -= rollSpeed
} else if (direction === 0) { // 下
if (this.yPos >= 0) {
this.$emit('ScrollEnd')
this.yPos = h * -1
}
this.yPos += rollSpeed
}
if (this.singleWaitTime) clearTimeout(this.singleWaitTime)
if (!!this.realSingleStopHeight) { //是否啟動了單行暫停配置
if (Math.abs(this.yPos) % this.realSingleStopHeight < rollSpeed) { // 符合條件暫停rollInterval
this.singleWaitTime = setTimeout(() => {
this._move()
}, rollInterval)
} else {
this._move()
}
} else {
this._move()
}
}.bind(this)
)
},
_initMove () {
this.$nextTick(() => {
this.copyHtml = '' //清空copy
this.height = this.$refs.wrap.offsetHeight
this.width = this.$refs.wrap.offsetWidth
let slotListWidth = this.$refs.slotList.offsetWidth
const { switchDelay } = this.options
const { openRoll} = this
this.$refs.realBox.style.width = slotListWidth + 'px'
this.realBoxWidth = slotListWidth
if (!openRoll) {
this.ease = 'linear'
this.delay = switchDelay
return
}
// 是否可以滾動判斷
if (this.scrollSwitch) {
let timer
if (timer) clearTimeout(timer)
this.copyHtml = this.$refs.slotList.innerHTML
setTimeout(() => {
this.realBoxHeight = this.$refs.realBox.offsetHeight + (this.$refs.wrapli.length>0?this.$refs.wrapli[0].offsetTop:0)
this._move()
}, 0);
} else {
this._cancle()
this.yPos = this.xPos = 0
}
})
},
changeStyle(){
}
},
mounted () {
this._initMove()
},
watch: {
data (newData, oldData) {
//監聽data是否有變更
if (!this.arrayEqual(newData, oldData)) {
this._cancle()
this._initMove()
}
}
},
beforeDestroy () {
this._cancle()
}
})
素材圖片:

最終實現效果圖如下:

isAbsloute
