商丘做网站,商丘网站优化,商丘网络推广,商丘网络公司
当前位置:首页 > 新闻资讯 > 建站经验 >

网页中文本朗读功能开发实现分享

发表日期:2018-01-14 19:49聚圣源浏览次数: 本文关键词:网页,中,文本,朗读,功能,开发,实现,分享,

  前几天完成了一个需求,在网页中完成鼠标指向哪里,就用语音读出所指的文本。如果是按钮、链接、文本输入框,则还还要给出是什么的提醒。同时针对大段的文本,不能整段的去读,要按照标点符号进行断句处理。

  重点当然就是先获取到当前标签上的文本,再把文本转化成语音即可。

  标签朗读

  这个很简单了,只用根据当前是什么标签,给出提示即可。

  // 标签朗读文本

  var tagTextConfig = {

  'a': '链接',

  'input[text]': '文本输入框',

  'input[password]': '密码输入框',

  'button': '按钮',

  'img': '图片'

  };

  还有需要朗读的标签,继续再添加即可。

  然后根据标签,返回前缀文本即可。

  /**

  * 获取标签朗读文本

  * @param {HTMLElement} el 要处理的HTMLElement

  * @returns {String} 朗读文本

  */

  function getTagText(el) {

  if (!el) return '';

  var tagName = el.tagName.toLowerCase();

  // 处理input等多属性元素

  switch (tagName) {

  case 'input':

  tagName += '[' + el.type + ']';

  break;

  default:

  break;

  }

  // 标签的功能提醒和作用应该有间隔,因此在最后加入一个空格

  return (tagTextConfig[tagName] || '') + ' ';

  }

  获取完整的朗读文本就更简单了,先取标签的功能提醒,再取标签的文本即可。

  文本内容优先取 title 其次 alt 最后 innerText。

  /**

  * 获取完整朗读文本

  * @param {HTMLElement} el 要处理的HTMLElement

  * @returns {String} 朗读文本

  */

  function getText(el) {

  if (!el) return '';

  return getTagText(el) + (el.title || el.alt || el.innerText || '

  }

  这样就可以获取到一个标签的功能提醒和内容的全部带朗读文本了。

  正文分隔

  接下来要处理的就是正文分隔了,在这个过程中,踩了不少坑,走了不少弯路,好好记录一下。

  首先准备了正文分隔的配置:

  // 正文拆分配置

  var splitConfig = {

  // 内容分段标签名称

  unitTag: 'p',

  // 正文中分隔正则表达式

  splitReg: /[,;,;。]/g,

  // 包裹标签名

  wrapTag: 'label',

  // 包裹标签类名

  wrapCls: 'speak-lable',

  // 高亮样式名和样式

  hightlightCls: 'speak-help-hightlight',

  hightStyle: 'background: #000!important; color: #fff!important'

  };

  最开始想的就是直接按照正文中的分隔标点符号进行分隔就好了呀。

  想法如下:

  获取段落全部文本

  使用 split(分隔正则表达式) 方法将正文按照标点符号分隔成小段

  每个小段用标签包裹放回去即可

  然而理想很丰满,现实很骨感。

  两个大坑如下:

  split 方法进行分隔,分隔后分隔字符就丢了,也就是说把原文的一些标点符号给弄丢了。

  如果段落内还存在其他标签,而这个标签内部也正好存在待分隔的标点符号,那包裹分段标签时直接破换了原标签的完整性。

  关于第一个问题,丢失标点的符号,考虑过逐个标点来进行和替换 split 分隔方法为逐个字符循环来做。

  前者问题是原本一次完成的工作分成了多次,效率太低。第二种感觉效率更低了,分隔本来是很稀疏的,但是却要变成逐个字符出判断处理,更关键的是,分隔标点的位置要插入包裹标签,会导致字符串长度变化,还要处理下标索引。代码是机器跑的,或许不会觉得烦,但是我真的觉得好烦。如果这么干,或许以后哪个AI或者同事看到这样的代码,说不定会说“这真是个傻xxxx”。

  第二个问题想过很多办法来补救,如先使用正则匹配捕获内容中成对的标签,对标签内部的分隔先处理一遍,然后再处理整个的。

  想不明白问题二的,可参考一下待分隔的段落:

  

这是一段测试文本,这里有个链接。您好,可以点击此处进行跳转还有其他内容其他内容容其他内容容其他内容,容其他内容。

 

  如先使用/<((\w+?)>)(.+?)<\/\2(?=>)/g 正则,依次捕获段落内被标签包裹的内容,对标签内部的内容先处理。

  但是问题又来了,这么处理的都是字符串,在js中都是基本类型,这些操作进行的时候都是在复制的基础上进行的,要修改到原字符串里去,还得记录下原本的开始结束位置,再将新的插进去。繁,还是繁,但是已经比之前逐个字符去遍历的好,正则捕获中本来就有了匹配的索引,直接用即可,还能接受。

  但是这只是处理了段落内部标签的问题,段落内肯定还有很多文本是没有处理呢,怎么办?

  正则匹配到了只是段落内标签的结果啊,外面的没有啊。哦,对,有匹配到的索引,上次匹配到的位置加上上次处理的长度,就是一段直接文本的开始。下一次匹配到的索引-1就是这段直接文本的结束。这只是匹配过程中的,还有首尾要单独处理。又回到烦的老路上去了。。。

  这么烦,一个段落分隔能这么繁琐,我不信!

  突然想到了,有文本节点这么个东西,删繁就简嘛,正则先到边上去,直接处理段落的所有节点不就行了。

  文本节点则分隔直接包裹,标签节点则对内容进行包裹,这种情况下处理的直接是dom,更省事。

  文本节点里放标签?这是在开玩笑么,是也不是。文本节点里确实只能放文本,但是我把标签直接放进去,它会自动转义,那最后再替换出来不就行了。

  好了,方案终于有了,而且这个方案逻辑多简单,代码逻辑自然也不会烦。

  /**

  * 正文内容分段处理

  * @param {jQueryObject/HTMLElement/String} $content 要处理的正文jQ对象或HTMLElement或其对应选择器

  */

  function splitConent($content) {

  $content = $($content);

  $content.find(splitConfig.unitTag).each(function (index, item) {

  var $item = $(item),

  text = $.trim($item.text());

  if (!text) return;

  var nodes = $item[0].childNodes;

  $.each(nodes, function (i, node) {

  switch (node.nodeType) {

  case 3:

  // text 节点

  // 由于是文本节点,标签被转义了,后续再转回来

  node.data = '<' + splitConfig.wrapTag + '>' +

  node.data.replace(splitConfig.splitReg, '$&<' + splitConfig.wrapTag + '>') +

  '';

  break;

  case 1:

  // 元素节点

  var innerHtml = node.innerHTML,

  start = '',

  end = '';

  // 如果内部还有直接标签,先去掉

  var startResult = /^<\w+?>/.exec(innerHtml);

  if (startResult) {

  start = startResult[0];

  innerHtml = innerHtml.substr(start.length);

  }

  var endResult = /<\/\w+?>$/.exec(innerHtml);

  if (endResult) {

  end = endResult[0];

  innerHtml = innerHtml.substring(0, endResult.index);

  }

  // 更新内部内容

  node.innerHTML = start +

  '<' + splitConfig.wrapTag + '>' +

  innerHtml.replace(splitConfig.splitReg, '$&<' + splitConfig.wrapTag + '>') +

  '' +

  end;

  break;

  default:

  break;

  }

  });

  // 处理文本节点中被转义的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 事件来完成。

  原因:

  不冒泡,不会触发父元素的再次朗读

  不重复触发,一个元素内移动时不会重复触发。

  /**

  * 在页面上写入高亮样式

  */

  function createStyle() {

  if (document.getElementById('speak-light-')) return;

  var = document.createElement('

  .id = 'speak-light-';

  .innerText = '.' + splitConfig.hightlightCls + '{' + splitConfig.hightStyle + '}';

  document.getElementsByTagName('head')[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) {

  return;

  }

  // 图片样式单独处理 其他样式统一处理

  if (e.target.nodeName.toLowerCase() === 'img') {

  $target.css({

  border: '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) {

  return;

  }

  // 图片样式

  if (e.target.nodeName.toLowerCase() === 'img') {

  $target.css({

  border: 'none'

  });

  } else {

  $target.removeClass(splitConfig.hightlightCls);

  }

  // 停止语音

  stopSpeak();

  });

  // 段落内文本朗读

  $(document).on('mouseenter.speak-help', '.' + splitConfig.wrapCls, function (e) {

  $(this).addClass(splitConfig.hightlightCls);

  // 开始朗读

  speakText(getText(this));

  }).on('mouseleave.speak-help', '.' + splitConfig.wrapCls, function (e) {

  $(this).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 : 语言 Gets and sets the language of the utterance.

  pitch : 音高 Gets and sets the pitch at which the utterance will be spoken at.

  rate : 语速 Gets and sets the speed at which the utterance will be spoken at.

  text : 文本 Gets and sets the text that will be synthesised when the utterance is spoken.

  voice : 声音 Gets and sets the voice that will be used to speak the utterance.

  volume : 音量 Gets and sets the volume that the utterance will be spoken at.

  onboundary : 单词或句子边界触发,即分隔处触发 Fired when the spoken utterance reaches a word or sentence boundary.

  onend : 结束时触发 Fired when the utterance has finished being spoken.

  onerror : 错误时触发 Fired when an error occurs that prevents the utterance from being succesfully spoken.

  onmark : Fired when the spoken utterance reaches a named SSML "mark" tag.

  onpause : 暂停时触发 Fired when the utterance is paused part way through.

  onresume : 重新播放时触发 Fired when a paused utterance is resumed.

  onstart : 开始时触发 Fired when the utterance has begun to be spoken.

  SpeechSynthesis : 用于朗读

  paused : Read only 是否暂停 A Boolean that returns true if the SpeechSynthesis object is in a paused state.

  pending : Read only 是否处理中 A Boolean that returns true if the utterance queue contains as-yet-unspoken utterances.

  speaking : Read only 是否朗读中 A Boolean that returns true if an utterance is currently in the process of being spoken — even if SpeechSynthesis is in a paused state.

  onvoiceschanged : 声音变化时触发

  cancel() : 情况待朗读队列 Removes all utterances from the utterance queue.

  getVoices() : 获取浏览器支持的语音包列表 Returns a list of SpeechSynthesisVoice objects representing all the available voices on the current device.

  pause() : 暂停 Puts the SpeechSynthesis object into a paused state.

  resume() : 重新开始 Puts the SpeechSynthesis object into a non-paused state: resumes it if it was already paused.

  speak() : 读合成的语音,参数必须为SpeechSynthesisUtterance的实例 Adds an utterance to the utterance queue; it will be spoken when any other utterances queued before it have been spoken.

  详细api和说明可参考:

  MDN - SpeechSynthesisUtterance

  MDN - SpeechSynthesis

  那么上面的两个方法可以写为:

  var speaker = new window.SpeechSynthesisUtterance();

  var speakTimer,

  stopTimer;

  // 开始朗读

  function speakText(text) {

  clearTimeout(speakTimer);

  window.speechSynthesis.cancel();

  speakTimer = setTimeout(function () {

  speaker.text = text;

  window.speechSynthesis.speak(speaker);

  }, 200);

  }

  // 停止朗读

  function stopSpeak() {

  clearTimeout(stopTimer);

  clearTimeout(speakTimer);

  stopTimer = setTimeout(function () {

  window.speechSynthesis.cancel();

  }, 20);

  }

  因为语音合成本来是个异步的操作,因此在过程中进行以上处理。

  现代浏览器已经内置了这个功能,两个API接口兼容性如下:

  Feature

  Chrome

  Edge

  Firefox (Gecko)

  Internet Explorer

  Opera

  Safari

  (WebKit) Basic

  support 33

  (Yes)

  49 (49)

  No support

  ?

  7

  如果要兼容其他浏览器或者需要一种完美兼容的解决方案,可能就需要服务端完成了,根据给定文本,返回相应语音即可,百度语音 http://yuyin.baidu.com/docs就提供这样的服务。

如没特殊注明,文章均来自网络! 转载请注明来自:http://www.jushengyuan.com.cn/news/jzjy/9830.html

网站设计案例推荐

热门文章

站长工具综合查询里面SSL证书不...

SSL证书不安全是怎么回事?但是点进去这个提示后,显示的是别人的tdk是不是被劫持或者被黑了?...

日期:2018-03-27 浏览次数:1910

简洁而实用的版权信息可增色网...

打开网站,人们自然会从上往下浏览,首先映入眼帘的一定是BANNER主视觉,然后是网站主体内容部分。相比较而言,注意到网站底部版权信息的浏览者确实不多,但这并说明网站版权信...

日期:2018-09-20 浏览次数:1888

网站设计需要注意的3点问题...

对于网站设计来讲会受到很多因素的影响,而且不同类型的网站,在设计时需要体现的元素也不一样,比如说对于一个企业网站更想体现出品牌以及产品的特点,主要是对产品、企业信...

日期:2018-02-12 浏览次数:1808

企业网站为什么要改版 改版选择...

网站改版,是每个建站企业必须面临的工作。相信也有不少站长问,已经做好的企业网站,为何还要大费周章重新进行改版设计呢?下面小编就来给大家说说,企业究竟为什么要改变,并...

日期:2018-03-26 浏览次数:1342

永城网站制作:模板建站不可不...

随着互联网建站尤其是自助建站热潮不断高涨,建站行业涌现出越来越多的自助建站平台,这些平台普遍都提供网站模板可选。...

日期:2018-04-30 浏览次数:940

相关文章

【吉林做网站】构筑企业微中心...

互联网时代,企业通过官网链接更多客户,可以说,官网则就是企业的门面,是品牌企业的标志,所以权威度是和企业一致的。 对于企业而言,官网究竟有多重要? 1.提高可信度 品牌官网能够...

日期:2018-09-12 浏览次数:63

【黑龙江网站优化】Css中color属...

我们大家在制作网站、编写css代码的时候,总会时不时的用到color属性。作为网页制作过程中最常用到的属性之一 ,大家知道color有几种表达方法吗?今天我就带大家一起来看一下。 第...

日期:2018-09-03 浏览次数:64

【新疆网络推广】从建站开始做...

如果说要做好SEO,那一定要从建站系统开始,如果说建站系统里SEO效果做的最好的,那一定是云指建站。 影响一个网站的SEO优化的因素主要是:服务器稳定性、网站的框架、网站代码、...

日期:2018-08-27 浏览次数:74

【新疆网络推广】从建站开始做...

如果说要做好SEO,那一定要从建站系统开始,如果说建站系统里SEO效果做的最好的,那一定是云指建站。 影响一个网站的SEO优化的因素主要是:服务器稳定性、网站的框架、网站代码、...

日期:2018-08-26 浏览次数:70

商丘网:从建站开始做SEO,让您的...

如果说要做好SEO,那一定要从建站系统开始,如果说建站系统里SEO效果做的最好的,那一定是云指建站。 影响一个网站的SEO优化的因素主要是:服务器稳定性、网站的框架、网站代码、...

日期:2018-08-18 浏览次数:72

随机推荐

站长,为何不干点靠谱又赚钱的...

【重庆网站制作】传统企业如何...

确保用户重复访问的简单步骤...

提交世纪佳缘漏洞后被抓 “白帽...

百度站长平台官方说法:关于...

移动互联网带来新需求 企业手机...