目录
利用webapi speechSynthesis帮助我们自动郎读英语单词,可以利用这个API,做一些小说朗读或到账提示。

用Vue写了一个简单页面,里面还写了一个简单的虚拟Table支持海量数据展示。
20231106-223410
里面的all.js文件是英语四级的单词,在文章内自行下载,也可以去这里面把JSON下载。
GitHub - cuttlin/Vocabulary-of-CET-4: 英语四级词库
复制里面的代码,放到html文件就可以运行
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Documenttitle>
-
-
- <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>