发布时间:2022-5-1 分类: 行业资讯
几天前,我完成了一项要求。当我在网页中完成鼠标指针时,我用语音来阅读文本。如果它是一个按钮,一个链接或一个文本输入框,它也会提醒它是什么。同时,对于大部分文本,不可能阅读整个段落,并且应该根据标点符号执行句子处理。
重点当然是将文本放在当前标签上,然后将文本转换为语音。
标签阅读
这很简单,只需根据当前可用的标签给出提示。
//标记阅读文本
Var tagTextConfig={
'a':'链接',
'input [text]':'文本输入框',
'input [password]':'密码输入框',
'按钮':'按钮',
'img':'图片'
};
还有一些标签需要大声朗读,您可以继续添加它们。
然后根据标签返回前缀文本。
/**
*获取标签阅读文本
* @param {HTMLElement}要处理的HTMLElement
* @returns {String}阅读文本
*/
函数getTagText(el){
如果(!el)返回'';
Var tagName=el.tagName.toLowerCase();
//处理多个属性元素,例如input
Switch(tagName){
案例'输入':
tagName +='['+ el.type +']';
打破;
默认为:
打破;
}
//标签的功能提醒和功能应该有一个间隔,所以在末尾添加一个空格
返回(tagTextConfig [tagName] ||'')+'';
}
获得完整的文本阅读更简单。首先,获取标签的功能提醒,然后获取标签的文本。
文本内容优先。然后alt最后一个innerText。
/**
*获取完整阅读文本
* @param {HTMLElement}要处理的HTMLElement
* @returns {String}阅读文本
*/
函数getText(el){
如果(!el)返回'';
返回getTagText(el)+(el.title || el.alt || el.innerText ||'');
}
通过这种方式,您可以使用大声文本获取标记的功能提醒和内容的所有内容。
身体分离
接下来要处理的是文本的分离。在这个过程中,我踩了很多坑,走了很多弯路,并记录下来。
首先准备以身体分离的配置:
//身体分割配置
Var splitConfig={
//内容细分标签名称
unitTag:'p',
//在正文中分隔正则表达式
splitReg:/[;,;。 ] /克,
//包标签名称
wrapTag:'label',
//包标签类名称
wrapCls:'speak-lable',
//突出显示样式名称和样式
hightlightCls:'speak-help-hightlight',
hightStyle:'背景: 000!重要;颜色: fff!important'
};
我想要考虑的第一件事是根据文本中分隔的标点符号直接将它们分开。
这个想法如下:
获取段落的全文
使用split(单独的正则表达式)方法通过标点符号将正文分成小段落
每个小件都用标签包裹,可以退回
然而,理想是充实的,现实是非常瘦。
两个大坑如下:
拆分方法是分开的,分隔符后分隔符会丢失,这意味着原始文本的某些标点符号会丢失。
如果段落中还有其他标记,并且在标记内部还有要分隔的标点符号,则在分割标记标签时会直接销毁原始标记的完整性。
关于第一个问题,标点符号丢失,并且标点符号被认为已完成并被替换。拆分分离方法是逐个字符完成的。
前一个问题是最初完成的工作被分成多次,效率太低。第二种感觉效率较低。分离非常稀少,但必须逐一判断。更重要的是,分离的标点符号的位置被插入到包标签中,这将导致字符串的长度发生变化。处理下标索引。代码在机器上运行,也许并不烦人,但我真的很生气。如果你这样做,也许将来哪个AI或同事会看到这样的代码,也许你会说“这真是一个愚蠢的xxxx”。
第二个问题我想到了很多方法来补救,例如使用常规匹配来捕获内容中的配对标签,首先处理标签内的分离,然后处理整个。
如果你不明白问题2,请参考段落:
这是一个测试文本,这里有一个链接。您好,您可以点击这里跳转和其他内容。其他内容允许其他内容容纳其他内容和其他内容。
如果你使用/<((w+?)>)(.+?)</2(?=>)/g常规,你将捕获段落中包含的段落的内容,然后处理首先是标签的内容。 。
但是问题又来了,所以字符串被处理了,它是js中的基本类型,这些操作是在复制的基础上进行的,要修改成原始字符串,你必须记录原始字符串的开头。位置,新的将被插入。它很复杂,但它仍然很复杂,但最好是之前从一个角色到另一个角色。在常规捕获中,有一个匹配的索引,可以直接使用并可以接受。
但这只是处理段落内部标签的问题。段落中肯定有很多未经处理的文本。我该怎么办?
常规匹配只是段落内标签的结果,没有外部。哦,是的,有一个匹配的索引,最后一个匹配的位置加上最后一个处理的长度,是一个直文的开头。 index-1的下一个匹配是此直接文本的结尾。这仅在匹配过程中,第一个和最后一个是分开处理的。回到烦人的老路上。 。 。
这么烦人,段落分离可能会如此繁琐,我不相信!
突然想到它,有一个文本节点这样的东西,很简单的删除复杂,经常先到边缘,直接处理段落的所有节点是不够的。
文本节点分隔直接宗地,标签节点包装内容。在这种情况下,处理直接dom,这是更麻烦的。
在文本节点中放置标签?这是个笑话,也不是。文本节点只能放文本,但我将标签直接放入其中,它会自动转义,然后不会被替换。
好的,解决方案终于存在了,这个解决方案的逻辑很简单,代码的逻辑自然不会烦人。
/**
*正文内容细分处理
* @param {jQueryObject/HTMLElement/String} $ content要处理的正文jQ对象或HTMLElement或其对应的选择器
*/
函数splitConent($ content){
$ content=$($ content);
$ content.find(splitConfig.unitTag).each(function(index,item){
Var $ item=$(item),
Text=$ .trim($ item.text());
如果(!text)返回;
Var nodes=$ item [0] .childNodes;
$ .each(nodes,function(i,node){
Switch(node.nodeType){
案例3:
//文本节点
//由于它是一个文本节点,标签将被转义,然后再转回
Node.data='<'+ splitConfig.wrapTag +'>'+
Node.data.replace(splitConfig.splitReg,'$&<'+ splitConfig.wrapTag +'>')+
“”;
打破;
案例1:
//元素节点
Var innerHtml=node.innerHTML,
开始='',
结束='';
//如果里面有直接标签,请先将其删除
Var startResult=/^< w +?> /。exec(innerHtml);
如果(startResult){
Start=startResult [0];
innerHtml=innerHtml.substr(start.length);
}
Var endResult=/</w +?> $ /.exec(innerHtml);
如果(endResult){
结束=endResult [0];
innerHtml=innerHtml.substring(0,endResult.index);
}
//更新内部内容
node.innerHTML=start +
'<'+ splitConfig.wrapTag +'>'+
innerHtml.replace(splitConfig.splitReg,'$&<'+ splitConfig.wrapTag +'>')+
''+
端;
打破;
默认为:
打破;
}
});
//处理文本节点中的转义html标记
$ item [0] .innerHTML=$ item [0] .innerHTML
.replace(new RegExp('<'+ splitConfig.wrapTag +'>','g'),'<'+ splitConfig.wrapTag +'>')
.replace(new RegExp('< /'+ splitConfig.wrapTag +'>','g'),'');
$ item.find(splitConfig.wrapTag).addClass(splitConfig.wrapCls);
});
}
上面代码中的最后一个代码在文本节点中替换转义的包标签似乎有点麻烦,但是在ES5 JavaScript不支持常规后断言(即正则表达式中)之前没有办法;后来”的)。因此,没有办法准确地替换<和>包标签之前和之后,只用标签名称替换它。
事件处理
在上文中,完成了文本获取和段落分离。以下是获取文本以在鼠标向上移动时触发读取,并在移除鼠标时停止读取。
鼠标移动,只读一次,原因有两个,使用mouseenter和mouseleave事件。
原因是:
不起泡,不会触发父元素再次读取
触发器不会重复,并且在元素内移动时不会重复触发器。
/**
*在页面上写下高亮风格
*/
函数createStyle(){
如果(document.getElementById('speak-light-style'))返回;
Var style=document.createElement('style');
Style.id='speak-light-style';
style.innerText='。'+ splitConfig.hightlightCls +'{'+ splitConfig.hightStyle +'}';
document.getElementsByTagName(”头”)[0] .appendChild(风格);
}
//需要以非文本逗号分隔的标签
Var speakTags='a,p,span,h1,h2,h3,h4,h5,h6,img,input,button';
$(document).on('mouseenter.speak-help',speakTags,function(e){
Var $ target=$(e.target);
//在段落中排除
if($ target.parents('。'+ splitConfig.wrapCls)。length || $ target.find('。'+ splitConfig.wrapCls).length){
返回;
}
//图像样式分开处理。其他样式统一处理
如果(e.target.nodeName.toLowerCase()==='img'){
$ target.css({
边界:'2px solid 000'
});
} else {
$ target.addClass(splitConfig.hightlightCls);
}
//开始大声朗读
speakText(的getText(e.target));
})。on('mouseleave.speak-help',speakTags,function(e){
Var $ target=$(e.target);
if($ target.find('。'+ splitConfig.wrapCls).length){
返回;
}
//图片样式
如果(e.target.nodeName.toLowerCase()==='img'){
$ target.css({
边界:'无'
});
} else {
$ target.removeClass(splitConfig.hightlightCls);
}
//停止发声
stopSpeak();
});
//段落中的文字是大声朗读
$(document).on('mouseenter.speak-help','。'+ splitConfig.wrapCls,function(e){
$(本).addClass(splitConfig.hightlightCls);
//开始大声朗读
speakText(的getText(本));
})。on('mouseleave.speak-help','。'+ splitConfig.wrapCls,function(e){
$(本).removeClass(splitConfig.hightlightCls);
//停止发声
stopSpeak();
});
小心将段落的语音处理与其他地方分开。为什么?因为段落是块级元素,当鼠标移动到段落中的空白时,例如:段落前后的空格,第一行的缩进,最后一行的剩余空白等,它不应该大声朗读。如果它没有被阻止,这些区域将直接触发整段文本的阅读,失去我们对段落文本分离的意义,而无论以何种方式转换语音都是时间,大部分内容可能需要很长一段时间,影响了语音输出的体验。
文字合成语音
上面我们直接用两种方法SpeakText(text)和stopSpeak()来触发语音的读取和停止。
让我们看看如何实现这两个功能。
事实上,现代浏览器默认已提供上述功能:
Var speechSU=new window.SpeechSynthesisUtterance();
speechSU.text='你好,世界!';
window.speechSynthesis.speak(speechSU);
将其复制到浏览器控制台以查看是否可以听到声音(需要Chrome 33 +,Firefox 49+或IE-Edge)
使用这两个API:
用于语音合成的SpeechSynthesisUtterance
Lang: Language获取并设置话语的语言。
音高: pitch获取并设置话语的音高。
Rate: Speech rate获取和设置发言时的速度。
Text: Text获取并设置在说出话语时将合成的文本。
Voice: Sound获取并设置将用于说出话语的声音。
Volume: volume获取并设置话语将被说出的音量。
Onboundary:单词或句子边界触发器,即声音话语到达单词或句子边界。
Onend:在说完话语时发火。
发生错误时会触发错误:,从而无法成功说出话语。
Onmark:当说出的话语到达命名的SSML“标记”标记时触发。
Onpause:在话语暂停时暂停。
Onresume:重播时触发当暂停的话语恢复时触发。
当开始说话时,Onstart:以Fired开头。
用于朗读的SpeechSynthesis:
已暂停:只读暂停布尔值,如果SpeechSynthesis对象处于暂停状态,则返回true。
待定:只读是否布尔值如果话语队列包含尚未说出的话语则返回true。
说到:只读一个布尔值,如果一个话语当前正处于被说出的过程中,则返回true—即使SpeechSynthesis处于暂停状态。
Onvoiceschanged:声音改变时触发
取消():情境待定队列从话语队列中删除所有话语。
getVoices():获取浏览器支持的语音包列表返回表示当前设备上所有可用语音的SpeechSynthesisVoice对象列表。
Pause(): Pause将SpeechSynthesis对象置于暂停状态。
Resume(): Restart将SpeechSynthesis对象置于非暂停状态:如果已暂停则恢复它。
Speak():读取合成语音。参数必须是SpeechSynthesisUtterance的实例。当其他话语在被说出之前排队时,就会说出来。
详细的api和描述可以在:
中找到MDN - SpeechSynthesisUtterance
MDN - SpeechSynthesis
那么上面两种方法可以写成:
Var speaker=new window.SpeechSynthesisUtterance();
Var speakTimer,
stopTimer;
//开始大声朗读
函数speakText(text){
clearTimeout(speakTimer);
window.speechSynthesis.cancel();
speakTimer=setTimeout(function(){
Speaker.text=text;
window.speechSynthesis.speak(扬声器);
},200);
}
//停止阅读
函数stopSpeak(){
clearTimeout(stopTimer);
clearTimeout(speakTimer);
stopTimer=setTimeout(function(){
window.speechSynthesis.cancel();
},20);
}
由于语音合成本质上是异步操作,因此在该过程中执行上述处理。
现代浏览器内置了此功能,两个API接口的兼容性如下:
特征
铬
边缘
Firefox(Gecko)
Internet Explorer
戏
Safari浏览器
(WebKit)基本
支持33
(是)
49(49)
没有支持
?
7
如果您想与其他浏览器兼容或需要完美的兼容解决方案,则可能需要完成服务器。根据给定的文字,您可以返回相应的声音。百度语音http://yuyin.baidu.com/docs提供此类服务。 。