# contenteditable属性
使用了contenteditable属性的元素会有一个蓝色边框
如何去除这个样式
<style lang="css">
/* 去除 contenteditable 元素的默认边框 */
[contenteditable="true"]:focus {
outline: none;
border: none;
}
</style>
1
2
3
4
5
6
7
2
3
4
5
6
7
音频播放时对应文字高亮
<template>
<section style="width: 100%">
<h3>editor</h3>
<audio
@timeupdate="handleTimeUpdate"
controls
src="https://www.iflyrec.com/MediaStreamService/v1/files/HyjyPHJ-mz316425221292290048/data?token=3f417f133927480b805609a8524a3218"
></audio>
<div class="paragraph" :contenteditable="true" @click="handleActive">
<span
v-for="(item, index) in content"
:key="index"
:data-wb="item.time[0]"
:data-we="item.time[1]"
>{{ item.text }}</span
>
</div>
</section>
</template>
<script setup lang="ts">
import { getContent } from '@/api/api'
import { ref, useCssModule } from 'vue'
defineOptions({ name: 'EditorView' })
type ContentType = {
text: string
time: [number, number]
}
const content = ref<ContentType[]>([])
getContent().then((res) => {
const { transcriptResult } = res
const data = JSON.parse(transcriptResult)
const { words } = data.ps[0]
console.log('res: ', words)
content.value = words
})
const $style = useCssModule()
/**
* 高亮某个元素
* @param target 需要高亮的元素
*/
const highlight = (target: HTMLElement) => {
target.classList.add($style.active)
}
/**
* 清除高亮
*/
const clearHighlight = () => {
const spans = document.querySelectorAll('.paragraph span')
spans.forEach((span) => span.classList.remove($style.active))
}
const handleActive = (e: MouseEvent) => {
const target = e.target as HTMLElement
clearHighlight()
if (target.tagName === 'SPAN') {
highlight(target)
}
}
const handleTimeUpdate = (e: Event) => {
const target = e.target as HTMLAudioElement
const currentTime = Math.floor(target.currentTime * 1000)
console.log(currentTime)
highlightCurrentWord(currentTime)
}
const highlightCurrentWord = (currentTime: number) => {
clearHighlight()
const spans: NodeListOf<HTMLSpanElement> = document.querySelectorAll('.paragraph span')
spans.forEach((span) => {
const startTime = Number(span.dataset.wb)
const endTime = Number(span.dataset.we)
if (startTime && endTime) {
if (currentTime >= startTime && currentTime <= endTime) {
highlight(span)
}
}
})
}
</script>
<style lang="scss" module>
.active {
color: #fff;
background-color: #4499ff;
}
</style>
<style lang="css">
[contenteditable='true']:focus {
outline: none;
border: none;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
使用requestAnimationFrame优化
<template>
<section style="width: 100%">
<h3>editor</h3>
<audio
@timeupdate="handleTimeUpdate"
@play="handleAudioPlay"
@pause="handleAudioPause"
controls
src="https://www.iflyrec.com/MediaStreamService/v1/files/HyjyPHJ-mz316425221292290048/data?token=3f417f133927480b805609a8524a3218"
></audio>
<div class="paragraph" :contenteditable="true" @click="handleActive">
<span
v-for="(item, index) in content"
:key="index"
:data-wb="item.time[0]"
:data-we="item.time[1]"
>{{ item.text }}
</span>
</div>
</section>
</template>
<script setup lang="ts">
import { getContent } from '@/api/api'
import { ref, useCssModule } from 'vue'
defineOptions({ name: 'EditorView' })
type ContentType = {
text: string
time: [number, number]
}
const content = ref<ContentType[]>([])
const audioCurrentTime = ref<number>()
const isPlaying = ref<boolean>(false)
const lastTime = ref<number>()
getContent().then((res) => {
const { transcriptResult } = res
const data = JSON.parse(transcriptResult)
const { words } = data.ps[0]
console.log('res: ', words)
content.value = words
})
const $style = useCssModule()
/**
* 高亮某个元素
* @param target 需要高亮的元素
*/
const highlight = (target: HTMLElement) => {
target.classList.add($style.active)
}
/**
* 清除高亮
*/
const clearHighlight = () => {
const spans = document.querySelectorAll('.paragraph span')
spans.forEach((span) => span.classList.remove($style.active))
}
const handleActive = (e: MouseEvent) => {
const target = e.target as HTMLElement
clearHighlight()
if (target.tagName === 'SPAN') {
highlight(target)
}
}
const handleTimeUpdate = (e: Event) => {
const target = e.target as HTMLAudioElement
const currentTime = Math.floor(target.currentTime * 1000)
console.log(currentTime)
audioCurrentTime.value = currentTime
updateHighlight()
}
const highlightCurrentWord = (currentTime: number) => {
clearHighlight()
const spans: NodeListOf<HTMLSpanElement> = document.querySelectorAll('.paragraph span')
spans.forEach((span) => {
const startTime = Number(span.dataset.wb)
const endTime = Number(span.dataset.we)
if (startTime && endTime) {
if (currentTime >= startTime && currentTime <= endTime) {
highlight(span)
}
}
})
}
const updateHighlight = () => {
if (!isPlaying.value) return
if (audioCurrentTime.value && audioCurrentTime.value !== lastTime.value) {
highlightCurrentWord(audioCurrentTime.value)
lastTime.value = audioCurrentTime.value
}
requestAnimationFrame(updateHighlight)
}
const handleAudioPlay = () => {
isPlaying.value = true
requestAnimationFrame(updateHighlight)
}
const handleAudioPause = () => {
isPlaying.value = false
}
</script>
<style lang="scss" module>
.active {
color: #fff;
background-color: #4499ff;
}
</style>
<style lang="css">
[contenteditable='true']:focus {
outline: none;
border: none;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
关于动态添加active高亮样式,可以采用动态class来处理
<div class="paragraph" :contenteditable="true" @click="handleActive">
<span
v-for="(item, index) in content"
:class="{
[$style.active]:
audioCurrentTime &&
audioCurrentTime >= item.time[0] &&
audioCurrentTime <= item.time[1]
}"
:key="index"
:data-wb="item.time[0]"
:data-we="item.time[1]"
>{{ item.text }}
</span>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15