前端 日志自动滚动效果
一、需求
当某个程序在后端运行的时候,前端经常要显示一个动态的进度条,或者 loading 的动画,或者百分比的数字
当然,更好的效果是展示给用户程序运行的具体细节,比如运行中记录的日志
这就好像电视剧结束后播放的演员表一样
二、实现
一般来说,有两种方法来实现这个需求。
1、scrollIntoView()
scrollIntoView()方法会滚动元素的父容器,使调用scrollIntoView的元素在父元素中可见。
语法如下:
element.scrollIntoView(); // 等同于element.scrollIntoView(true)
element.scrollIntoView(alignToTop); // Boolean型参数
element.scrollIntoView(scrollIntoViewOptions); // Object型参数
参数如下:
参数 | 类型 | 默认值 | 备注 |
---|---|---|---|
alignToTop | boolean | true | 该元素的顶端是否和其所在滚动区的可视区域的顶端对齐 |
scrollIntoViewOptions | object | {behavior: “auto”, block: “start”, inline: “nearest”} | behavior:定义滚动动画过度,可选auto 、smooth ;block:定义垂直方向的对齐,可选 start , center , end , 或 nearest ;inline:定义水平方向的对齐,可选 start , center , end , 或 nearest |
使用方法:
<template>
<div >
<strong>进程日志</strong>
<div style="max-height:120px; position:relative">
<div v-if="logs.length">
<p
class="logList-item"
v-for="(item, index) in logs"
:key="index"
:id="(logs.length === index+1)?'scollLog':''"
>{{ item }}</p>
</div>
<p v-else>暂无数据</p>
</div>
</div>
</template>
<script lang='ts'>
import { Component, Vue, Watch, Prop } from 'vue-property-decorator'
import { formatTime } from '@/utils'
@Component
export default class extends Vue {
private name: string = 'processLog';
private logs: Array<string> = [];
@Prop() private LOGS: any;
@Prop() private scrollT: any;
// getData 将父组件传递过来的日志转成 `[2021-01-01 12:12:12] 系统正在启动` 这种格式
private getData () {
this.logs = this.LOGS
? this.LOGS.map(
(item: object): string =>
'[' + formatTime(item.updatedTime) + '] ' + item.content + '\n'
)
: []
}
@Watch('LOGS')
scrollLogs (newValue: any) {
this.getData()
// 在日志渲染之后,将最底下的日志滚动到其父元素可视区域内
this.$nextTick(() => {
let top = 160
if (newValue.length !== 0 && this.scrollT < top) {
(document.getElementById('scollLog') as HTMLElement).scrollIntoView({ behavior: 'smooth', block: 'nearest' })
}
})
}
mounted () {
this.getData()
}
}
</script>
<style lang="scss" scoped>
.logList-item {
padding: 8px 0;
margin: 0;
}
</style>
总结
这个方法的对 ios safari 和 IE 不是很友好,其他的浏览器没有什么问题
另外,这个方法对布局也没有什么要求,简单方便,易于理解,不管后端返回的是全量数据还是累加数据,只需要针对最后一条渲染的日志调用即可
2、scrollTo
这个方法非常常用,语法和参数就略过了,需要注意的是这个方法对布局有要求,布局不对,这个方法是实现不了滚动的
使用方法
<template>
<div class="console-wraper">
<div class="console_window" ref="consoleWindow">
<div id="console_output" ref="consoleOutput">
<-- 这里写日志信息,注意,一定要在父元素外再套一层-->
</div>
</div>
</div>
</template>
<script>
/* eslint-disable camelcase */
import { postRest } from '@/utils/http'
export default {
data() {
return {
sessionType: '',
sessionId: 0,
command: '',
}
},
methods: {
async getData() {
const res = await postRest(`/sessions/${sessionId}/shell`, { read: true, cmd: command })
if (!res) return
this.command = ''
this.cleanData(res)
},
cleanData(res) {
const { con_prompt, con_update, con_append } = res
this.consolePrompt = unescape(con_prompt)
try {
let conAppend = JSON.parse(con_append)
if (conAppend.length) {
conAppend.forEach(item => {
this.printLine(unescape(item[1]), item[0])
})
} else {
if (con_update.length) {
this.printLine(unescape(con_update), 'output_line')
}
}
} catch (e) {
console.log('error')
}
},
printLine(s, type) {
if ((s = String(s))) {
let n = document.createElement('pre')
if (!type) type = 'output_line'
// IE has to use innerText
if (n.innerText !== undefined) {
n.innerText = s
// Firefox uses createTextNode
} else {
n.appendChild(document.createTextNode(s))
}
n.className = type
this.$refs.consoleOutput.appendChild(n)
// 添加完日志后,让父元素滚动到其自身的距离
this.$refs.consoleWindow.scrollTop = this.$refs.consoleWindow.scrollHeight
}
},
help () {
this.printLine(' Web Console Internal Commands\n')
this.printLine('=========================================\n\n')
this.printLine(' /help Show this text\n')
this.printLine(' /clear Clear the screen\n')
this.printLine('\n')
},
clear () {
this.$refs.consoleOutput.innerHTML = ''
}
},
async mounted() {
this.getData()
}
}
</script>
<style lang=scss scoped>
.console-wraper{
display: flex;
flex-direction: column;
height: 100%;
}
.console_window {
flex: 1;
background: #333;
overflow: auto;
.console_output {
white-space: pre-wrap;
word-wrap: break-word;
padding: 12px;
font-weight: bold;
}
/deep/ pre.input_line {
font-size: 14px;
color: yellow;
opacity: 0.8;
padding: 0 20px;
}
/deep/ pre.output_line {
color: #fff;
font-size: 13px;
white-space: pre-wrap;
word-wrap: break-word;
padding-left: 40px;
opacity: 0.7;
}
}
</style>
喜欢可以收藏一下,哈哈哈