目录
本页面完整代码
视频演示
完整的页面代码
利用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 } = Vueconst useVirtualList = (options) => {const { rowHeight, height, dataSource, columnCount = 1 } = optionsconst 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.scrollconst rowKey = props.rowKeyconst 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.volumeutterThis.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>