前言
vue-seamless-scroll是一个基于Vue.js的无缝滚动插件,支持上下左右无缝滚动、单步滚动,以及支持水平方向的手动切换功能,配置多,满足日常开发需求,基于requestAnimationFrame实现,下面给大家介绍一下,具体的使用详细
一. 安装相关依赖
//vue2
import scroll from 'vue-seamless-scroll'
Vue.use(scroll)
//vue3 代码多去少补
import { createApp } from 'vue';
import App from './App.vue';
import vue3SeamlessScroll from "vue3-seamless-scroll";
const app = createApp(App);
app.use(vue3SeamlessScroll);
app.mount('#app');
二. 全局引入组件或者使用局部引入
//vue2
import scroll from 'vue-seamless-scroll'
Vue.use(scroll)
//vue3 代码多去少补
import { createApp } from 'vue';
import App from './App.vue';
import vue3SeamlessScroll from "vue3-seamless-scroll";
const app = createApp(App);
app.use(vue3SeamlessScroll);
app.mount('#app');
//组价使用三步骤1、引入 2、注册 3、使用
import vueSeamlessScroll from 'vue-seamless-scroll' //1、引入
components: { //2、注册
vueSeamlessScroll
},
<vue-seamless-scroll></vue-seamless-scroll> //3、使用
//vue3 同上 代码多去少补
import { defineComponent } from "vue";
import { Vue3SeamlessScroll } from "vue3-seamless-scroll";
export default defineComponent({
components: {
Vue3SeamlessScroll
}
})
<vue3-seamless-scroll></vue3-seamless-scroll>
三. 组件使用,配置相关属性
<template>
<!--
根目录规范(必须不能为空):
idm-ctrl:控件类型 drag_container:容器,drag_container_inlieblock:行内容器,idm_module:非容器的组件
id:使用moduleObject.id,如果id不使用这个将会被idm-ctrl-id属性替换
idm-ctrl-id:组件的id,这个必须不能为空
-->
<div idm-ctrl="idm_module" :id="moduleObject.id" :idm-ctrl-id="moduleObject.id" class="i-RollingList-outer"
ref="module_ref" :style="propData.heightType == 'adaptive' ? { height: moduleHeight + 'px' } : {}
">
<div class="i-RollingList-context">
<div class="headBox">
<div class="left">
季考核等次为“好”的人员
</div>
<!-- <div class="right" @click="onMoreUrl('')">
<el-tooltip class="item" effect="dark" content="更多" placement="bottom">
<i class="el-icon-more"></i>
</el-tooltip>
</div> -->
</div>
<div class="main" ref="parentElement">
<div @click="showInfo" class="content">
<vue-seamless-scroll v-if="dataList.length" :key="timeKey" :data="dataList" :class-option="scrollOption" class="info">
<div class="listBox">
<div v-for="(item,index) in dataList" :key="index" :data-index="index" >
<img :src="IDM.url.getModuleAssetsWebPath(require(`@/assets/icon1.png`))" alt=""> {{item}}
</div>
</div>
</vue-seamless-scroll>
<div v-else class="wsj">暂无数据</div>
</div>
</div>
</div>
</div>
</template>
<script>
import vueSeamlessScroll from 'vue-seamless-scroll'
//这个是放本地开发测试数据
const devResult = () => {
let data = {
"data": [
[
"[秘书处]:王老师,机老师",
"[干部人事处]:杜老师,彭老师,汪老师",
"[动员协调处]:王老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
"[秘书处]:王老师,机老师",
"[干部人事处]:杜老师,彭老师,汪老师",
"[动员协调处]:王老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
"[秘书处]:王老师,机老师",
"[干部人事处]:杜老师,彭老师,汪老师",
"[动员协调处]:王老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
"[政策法规处(审计监督室)]:李老师,陈老师",
]
],
"url": "http://localhost:8080/DreamWeb/ctrl/website/api/contentJump.html?contentId=240907161601EvCa9hieQyINrlIWQLY"
}
return data
}
export default {
name: 'WorkLedger',
components: { //2、注册
vueSeamlessScroll
},
data() {
return {
moduleObject: this._moduleObject || {},
moduleHeight: 0,
propData: this._propData?.compositeAttr || this.$root?.propData?.compositeAttr || {
heightType: "adaptive",
JumpType:"_block",
isJK: true,
customInterfaceUrl: "ctrl/p2531indexpc/indexDbData",
},
limitMoveNum:0,//滚动区域的高度
url:"",
dataList: []
},
computed: {
scrollOption(){
return {
step: 0.5, // 数值越大速度滚动越快
//这里动态获取的滚动的数据量,超出高度的时候滚动
limitMoveNum: Math.floor( (this.moduleHeight-66) / 30)+1, // 开始无缝滚动的数据量 this.list.length+1
hoverStop: true, // 是否开启鼠标悬停stop
direction: 1, // 0向下 1向上 2向左 3向右
openWatch: true, // 开启数据实时监控刷新dom
singleHeight: 30, // 单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1
singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
waitTime: 0, // 单步运动停止的时间(默认值1000ms)
}
}
},
props: {
_moduleObject: Object,
_propData: Object
},
created() {
if (process.env.NODE_ENV == "development") {
// this.login()
}
this.moduleHeight = this.propData.moduleHeight
this.moduleObject = this._moduleObject || this.$root.moduleObject;
this.resizeContentWrapperHeight(300);
},
mounted() {
//直接使用组件此处的回调必须的
this._moduleObject && IDM.callBackComponentMountComplete?.apply(this, [this._moduleObject]);
this.initData(true)
},
methods: {
//初始化数据
initData(flag) {
const param = this.commonParam();
if (!this.propData.dataSource || !this.propData.dataSource[0]?.id) {
if (this.moduleObject.env !== 'production') {
//开发时的数据源
this.dataList = devResult().data[0]
this.url=devResult().url
console.log(this.dataList, "==++++");
this.$nextTick().then(() => {
const parentHeight = this.$refs['parentElement'].offsetHeight || 0;
let limitMoveNum=Math.floor(parentHeight / 30)+1
console.log('父元素的高度:', parentHeight,limitMoveNum);
});
return;
}
}
console.log(this.propData.isJK, "是否使用接口");
if (this.propData.isJK == false) {
console.log('-----++++数据源数据');
IDM.datasource.request(this.propData.dataSource[0]?.id, {
moduleObject: this.moduleObject,
param
}, (data) => {
console.log(data, "+++++");
this.dataList = data.data[0] || {};
this.url=data.url
})
} else {
console.log('-----++++接口数据');
let url = this.propData.customInterfaceUrl //this.propData.customInterfaceUrl
let params = {
}
window.IDM.http.get(url, params).then((res) => {
console.log(res, res.data.data.url,"+++++");
this.dataList = res.data.data.data[0] || {};
this.url=res.data.data.url
}).catch(function (error) {
});
}
this.$nextTick().then(() => {
const parentHeight = this.$refs['parentElement'].offsetHeight || 0;
let limitMoveNum=Math.floor(parentHeight / 30)+1
console.log('父元素的高度:', parentHeight,limitMoveNum);
});
},
//在滚动区域的通过main来获取组件vue-seamless-scroll组件中每一行的点击事件
showInfo(e) {
console.log(e.target.dataset.index);
let idx = e.target.dataset.index;
console.log(this.dataList[idx]) //点击行数据
// e:鼠标事件,e.target:点击元素(<td>),e.target.parentNode:点击元素父元素(<tr>),e.target.parentNode.dataset:自定义属性位置
this.onItemClick({url:this.url})
},
//点击事件
onItemClick(config) {
console.log(config);
console.log("_______点击事件", this.propData.JumpType, config);
if (this.moduleObject.env !== 'production') {
console.log(456789);
return;
}
//自定义函数
if (this.propData.JumpType === 'custom') {
console.log(this.propData.JumpType);
this.customFunctionHandle(this.propData.customMoreBtnFunction, config);
return
} else if (this.JumpType === 'main') { //内嵌标签页打开
const item = {
isTabReload: "-1",
name: config.moduleName,
action: IDM.url.getWebPath(config.url),
target: "main",
parentName: ""
};
// window.$$iframeCtrl && window.$$iframeCtrl.addTab(item);
top.window.$$iframeCtrl.addTab(item)
} else if (config.url && config.url.length > 0) {
const url = IDM.url.getWebPath(config.url);
window.open(url, this.JumpType || '_block');
}
},
/**
* 提供父级组件调用的刷新prop数据
*/
propDataWatchHandle(propData) {
this.propData = propData.compositeAttr || {};
this.resizeContentWrapperHeight();
this.initData(true)
},
resizeContentWrapperHeight(wrapperHeight, wrapperWidth) {
wrapperHeight = wrapperHeight || $('#' + this.moduleObject.packageid)
.parents('.fsl-region-element')
.height();
wrapperWidth = wrapperWidth || $('#' + this.moduleObject.packageid)
.parents('.fsl-region-element')
.width();
let moduleTBMarginNumber = 0;
const iAttrArray = ['marginTopVal', 'marginBottomVal'];
iAttrArray.forEach(item => {
if (this.propData.box && this.propData.box[item]) {
if (this.propData.box[item].indexOf('%') > -1) {
//用宽度计算出实际的px
moduleTBMarginNumber =
moduleTBMarginNumber +
(parseFloat(this.propData.box[item].replace('%', '')) / 100) * wrapperWidth;
} else {
moduleTBMarginNumber =
moduleTBMarginNumber + parseFloat(this.propData.box[item].replace('px', ''));
}
}
});
this.moduleHeight = wrapperHeight - moduleTBMarginNumber;
},
/**0
* 组件通信:接收消息的方法
*/
receiveBroadcastMessage(messageObject) {
switch (messageObject.type) {
case 'regionResize':
if (messageObject.message && messageObject.message.gridEleTarget) {
const {
offsetHeight,
offsetWidth
} = messageObject.message.gridEleTarget
this.resizeContentWrapperHeight(offsetHeight, offsetWidth);
this.initData(true)
}
break;
case 'linkageResult':
if (messageObject.message) {
let data = this.propData.dataFiled ? this.getExpressData('data', this.propData.dataFiled, messageObject.message) : messageObject.message;
this.data = this.customFormat(this.propData.dataCustomFunction, data);
}
break;
}
},
/**
* 通用的获取表达式匹配后的结果
*/
getExpressData(dataName, dataFiled, resultData) {
//给defaultValue设置dataFiled的值
if (!dataName && dataName.length <= 0 && !resultData && resultData.length <= 0) {
return
}
let _defaultVal = undefined;
if (dataFiled) {
let filedExp = dataFiled;
filedExp = dataName + (filedExp.startsWiths('[') ? '' : '.') + filedExp;
let dataObject = { IDM: window.IDM };
dataObject[dataName] = resultData;
_defaultVal = window.IDM.express.replace.call(this, '@[' + filedExp + ']', dataObject, false);
}
return _defaultVal;
},
customFormat(customFunction, data) {
if (customFunction && customFunction[0] && customFunction[0].name) {
data =
window[customFunction[0].name] &&
window[customFunction[0].name].call(this, {
commonParam: this.commonParam(),
customParam: customFunction[0].param,
data
});
}
return data;
},
customFunctionHandle(customFunction, param = {}) {
let urlObject = window.IDM.url.queryObject();
let pageId = window.IDM.broadcast && window.IDM.broadcast.pageModule ? window.IDM.broadcast.pageModule.id : "";
var clickFunction = customFunction;
clickFunction && clickFunction.forEach((item1) => {
window[item1.name] &&
window[item1.name].call(this, {
urlData: urlObject,
pageId,
commonParam: this.commonParam(),
customParam: item1.param,
...param,
this: this,
});
});
},
commonParam() {
return {
moduleObject: this.moduleObject,
pageId: window.IDM.broadcast && window.IDM.broadcast.pageModule ?
window.IDM.broadcast.pageModule.id : '',
urlObject: IDM.url.queryObject()
};
},
}
};
</script>
<style lang="scss">
.i-RollingList-outer {
width: auto;
height: auto;
box-sizing: border-box;
margin: 15px 0 10px 10px;
margin-right: 3px;
.i-RollingList-context {
height: 100%;
width: 100%;
font-family: PingFangSC-Semibold;
font-size: 20px;
color: #FFFFFF;
.headBox {
height: 51px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.left {
position: relative;
height: 100%;
display: flex;
align-items: center;
font-weight: 600;
}
.right {
display: flex;
align-items: center;
cursor: pointer;
margin-right: 5px;
.el-icon-more {
font-size: 18px;
color: #CCD3E3;
}
}
.right:hover {
.el-icon-more {
color: #FFFFFF;
}
}
}
.main{
width: 100%;
height: calc(100% - 66px);
background: #F2F8FF;
border-radius: 10px;
.content{
font-family: PingFangSC-Regular;
font-size: 16px;
color: #333333;
line-height: 24px;
font-weight: 400;
height: 100%;
overflow: hidden;
.listBox{
&>div{
height: 30px;
display: flex;
align-items: center;
padding: 0 24px;
cursor: pointer;
img{
width: 13px;
height: 13px;
margin-right: 8px;
}
}
}
}
.wsj{
font-size: 16px;
color:#999;
text-align: center;
padding-top: 50px;
}
}
}
}</style>
//vue3 eg:
<vue3-seamless-scroll
hover-stop="true"
:list="listData"
hover="true"
step="0.3">
<div
v-for="(item, index) in listData"
:key="index"
class="item"
style="padding: 10px; margin: 5px; font-size: 14px"
>
<span
v-for="(value, key, ind) in item"
:key="ind"
class="spanClass">
{{value}}
</span>
</div>
</vue3-seamless-scroll>
四、注意事项
- vue-seamless-scroll
滚动数据data必须设置,否则将不会滚动。样式需要设置高度与overflow: hidden; - 单步运动停止的高度
单步运动停止的高度应该为单行数据高度的整数倍,在文章示例中就是<tr>高度的整数倍。当设置为整数倍时,列表文字都可以清楚展示看做为无缝滚动,否则就会出现文字展示不全的问题。 - 单行数据添加点击事件无效
一般情况下我们会把点击事件添加到循环行<tr>上,但是由于此组件的原因,点击事件会出现问题。解决方法,给父元素绑定事件,在子元素上进行事件捕获。
- 父元素绑定事件:父容器添加click事件showInfo,如果需要传入其他参数,showInfo可以改为showInfo($event, param),param为传入参数
- 在循环行上添加自定义的 data-x 属性
- 在事件showInfo中通过鼠标事件e与自定义属性进行查找
<div class="title-text">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<th>任务名称</th>
<th>执行人</th>
<th>当前进度</th>
</tr>
</table>
</div>
<div v-if="list.length" @click="showInfo" class="content">
<vue-seamless-scroll :key="timeKey" :data="list" :class-option="scrollOption" class="info">
<table border="0" cellpadding="0" cellspacing="0">
<tr v-for="(item, index) in list" :key="index" :data-index="index">
<td>{{item.title}}</td>
<td>{{item.name}}</td>
<td>{{item.progress}}</td>
</tr>
</table>
</vue-seamless-scroll>
</div>
<div v-else class="empty">暂无数据</div>
computed: {
scrollOption () {
return {
step: 0.5, // 数值越大速度滚动越快
limitMoveNum: 6, // 开始无缝滚动的数据量 this.list.length+1
hoverStop: true, // 是否开启鼠标悬停stop
direction: 1, // 0向下 1向上 2向左 3向右
openWatch: true, // 开启数据实时监控刷新dom
singleHeight: 21, // 单步运动停止的高度(默认值0是无缝不停止的滚动) direction => 0/1
singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动) direction => 2/3
waitTime: 2500 // 单步运动停止的时间(默认值1000ms)
}
}
},
页面存在定时器,数据实时刷新时,数据量(list.length)减少,由可滚动数据量变为不可滚动,页面显示依然滚动,limitMoveNum未生效。
在vue-seamless-scroll增加key属性,避免组件复用,每次数据请求时刷新key。key可以定义为当前时间(new Date().getTime())。
showInfo(e) {
let idx = e.target.parentNode.dataset.index;
console.log(this.list[idx]) //点击行数据
// e:鼠标事件,e.target:点击元素(<td>),e.target.parentNode:点击元素父元素(<tr>),e.target.parentNode.dataset:自定义属性位置
}
五、参考链接
vue-seamless-scroll官方文档
vue-seamless-scroll插件在线演示文档
vue-seamless-scroll的使用以及实例