目录
本页面完整代码
视频演示
完整的页面代码
利用webapi speechSynthesis帮助我们自动郎读英语单词,可以利用这个API,做一些小说朗读或到账提示。
本页面完整代码
用Vue写了一个简单页面,里面还写了一个简单的虚拟Table支持海量数据展示。
视频演示
20231106-223410
完整的页面代码
里面的all.js文件是英语四级的单词,在文章内自行下载,也可以去这里面把JSON下载。
GitHub - cuttlin/Vocabulary-of-CET-4: 英语四级词库
复制里面的代码,放到html文件就可以运行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="all.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.7/dist/vue.global.js"></script>
<style>
body{
background-color: rgba(0,0,0,0.04);
}
.table-wrapper{
background-color: #fff;
border: solid 1px #efefef;
box-shadow: 0 0px 3px 1px rgba(0,0,0,0.05);
}
.table-wrapper table {
width: 100%;
border-spacing: 0;
table-layout: fixed;
}
.header-table th {
background-color: #00a674;
height: 40px;
line-height: 40px;
color: rgb(158, 255, 205);
}
.body-table td {
background-color: #fff;
text-align: center;
}
.body-table tr:nth-of-type(n+2) td {
border-top: solid 1px rgba(0, 0, 0, 0.06);
}
.body-table tr:hover td {
background-color: #f7f7f7;
}
.form-wrap{
background-color: #fff;
margin-bottom: 15px;
padding: 15px;
box-shadow: 0 1px 3px 1px rgba(0,0,0,0.05);
}
.table-form {
table-layout:fixed;
}
.table-form th,.table-form td{
height: 25px;
line-height: 25px;
}
.table-form th{
width: 80px;
font-weight: 400;
font-size: 14px;
text-align: right;
}
.table-form th::after{
content:':';
}
</style>
</head>
<body>
<div id="app">
</div>
<template id="tplApp">
<div>
<div class="form-wrap">
<table class="table-form">
<tr>
<th>声音</th>
<td colspan="5"> <select v-model="voice.lang">
<option v-for="(v,i) in voices" :key="v.key" :value="v.name">{{v.name}}</option>
</select></td>
</tr>
<tr>
<th>语速</th>
<td><input v-model.number="voice.rate" type="number" min="0.1" max="10" step="0.1"/></td>
<th>音调</th>
<td><input v-model.number="voice.pitch" type="number" min="0" max="2" step="0.1"/></td>
<th>音量</th>
<td><input v-model.number="voice.volume" type="number" min="0" max="1" step="0.1"/></td>
</tr>
</table>
</div>
</div>
<Virtual-Table :columns="columns" :data-source="dataSource" row-key="word" :row-height="50" :scroll="scroll"></VirtualTable>
</div>
</template>
<script>
const { ref, shallowRef, h, toRaw, renderSlot,reactive,shallowReactive, toRefs, toRef, computed } = Vue
const useVirtualList = (options) => {
const { rowHeight, height, dataSource, columnCount = 1 } = options
const scrollTop = ref(0)
const onScroll = (e) => {
scrollTop.value = e.target.scrollTop
}
const scrollRowIndex = computed(() => {
return Math.floor(scrollTop.value / rowHeight.value)
})
const visibilityRowCount = computed(() => {
return Math.ceil(height / rowHeight.value)
})
const start = computed(() => {
return scrollRowIndex.value * columnCount
})
const end = computed(() => {
return start.value + visibilityRowCount.value * columnCount
})
const rowCount = computed(() => {
return Math.ceil(dataSource.value.length / columnCount)
})
const scrollHeight = computed(() => {
return rowCount.value * rowHeight.value
})
const currentList = computed(() => {
return dataSource.value.slice(start.value, end.value)
})
const containerProps = computed(() => {
return {
style: {
height: height + 'px',
overflowY: 'auto'
},
onScroll: onScroll
}
})
const invisibleHeight = computed(() => {
return (scrollRowIndex.value * rowHeight.value)
})
const scrollProps = computed(() => {
return {
style: {
height: scrollHeight.value + 'px',
paddingTop: invisibleHeight.value + 'px',
boxSizing: 'border-box',
},
}
})
return [{
containerProps,
scrollProps,
data: currentList
}]
}
const VirtualTable = {
props: ['columns', 'rowKey', 'dataSource', 'scroll', 'rowHeight'],
setup(props, { slots }) {
const rowHeight = toRef(props, 'rowHeight')
console.log('rowHeight',rowHeight.value)
const scroll = props.scroll
const rowKey = props.rowKey
const columns = toRef(props, 'columns')
const dataSource = toRef(props, 'dataSource')
const [{ containerProps, scrollProps, data: currentData }] = useVirtualList({
rowKey: rowKey,
rowHeight: rowHeight,
height: scroll.y,
dataSource: dataSource
})
const renderCol = (columns) => {
return h('colgroup', {}, columns.map((c, i) => {
return h('col', {
key: c.dataIndex || i,
style: {
...(c.width ? { width: c.width + 'px' } : {})
}
})
}))
}
const renderHeader = (columns) => {
return h('thead', {}, h('tr', {}, columns.map((c, i) => {
return h('th', {
key: c.dataIndex || i,
}, c.title)
})))
}
const renderCell = (columns, dataItem) => {
return columns.map((c, i) => {
return h('td', {
key: c.dataIndex || i,
}, c.render ? c.render(dataItem[c.dataIndex], dataItem, i) : dataItem[c.dataIndex])
})
}
const renderRow = (data) => {
return h('tbody', {}, data.map((d, i) => {
return h('tr', {
key: d[rowKey],
style: {
height: rowHeight.value + 'px'
}
}, renderCell(columns.value, d))
}))
}
return () => {
return h('div', {
class: 'table-wrapper'
},
h('div', {
class: 'header-wrap'
}, h('table', {
class: 'header-table'
},
renderCol(columns.value),
renderHeader(columns.value),
)),
h('div', {
class: 'body-wrap',
...containerProps.value
}, h('div', {
class: 'body-scroll-wrap',
...scrollProps.value
},
h('table', {
class: 'body-table'
},
renderCol(columns.value),
renderRow(currentData.value))
))
)
}
}
}
const app = Vue.createApp({
template: '#tplApp',
components:{
VirtualTable:VirtualTable
},
setup() {
const voices=shallowRef([])
const voice=shallowReactive({
lang:"",
pitch:1,
rate:1,
volume:1
})
speechSynthesis.addEventListener('voiceschanged', () => {
voices.value = speechSynthesis.getVoices()
voice.lang=voices.value[0].name
})
// 语音合成
const speak=(word, options = {})=> {
return new Promise((resolve, reject) => {
const utterThis = new SpeechSynthesisUtterance(word);
for (let i = 0; i < voices.value.length; i++) {
if ( voices.value[i].name === voice.lang) {
utterThis.voice = voices.value[i];
}
}
utterThis.pitch = voice.pitch;
utterThis.rate = voice.rate;
utterThis.volume = voice.volume
utterThis.onend = function () {
resolve()
}
utterThis.onerror = (e) => {
reject(e)
}
speechSynthesis.speak(utterThis);
})
}
const columns = shallowRef([
{
title: '单词',
dataIndex: 'word',
width: 220
},
{
title: '音标',
dataIndex: 'phonetic_symbol',
width: 220
},
{
title: '中文意思',
dataIndex: 'mean'
},
{
title: '操作',
width: 160,
render(v, record) {
return h('div',{
},h('button', {
onClick: () => {
speak(record.word)
}
}, '朗读单词'),h('button', {
style:{
marginLeft:'5px'
},
onClick: () => {
speak(record.mean)
}
}, '朗读中文'))
}
}
])
const dataSource = shallowRef(english_word_cet4_all)
return {
voices,
voice,
dataSource,
columns:columns,
scroll:{
y:window.innerHeight-150
}
}
}
})
app.mount('#app')
</script>
</body>
</html>