婷婷久久综合九色综合,欧美成色婷婷在线观看视频,偷窥视频一区,欧美日本一道道一区二区

<tt id="bu9ss"></tt>
  • <span id="bu9ss"></span>
  • <pre id="bu9ss"><tt id="bu9ss"></tt></pre>
    <label id="bu9ss"></label>

    當前位置:首頁 >  站長 >  建站經(jīng)驗 >  正文

    網(wǎng)頁中文本朗讀功能開發(fā)實現(xiàn)分享

     2017-12-12 09:14  來源: segmentfault   我來投稿 撤稿糾錯

      阿里云優(yōu)惠券 先領(lǐng)券再下單

    前幾天完成了一個需求,在網(wǎng)頁中完成鼠標指向哪里,就用語音讀出所指的文本。如果是按鈕、鏈接、文本輸入框,則還還要給出是什么的提醒。同時針對大段的文本,不能整段的去讀,要按照標點符號進行斷句處理。

    重點當然就是先獲取到當前標簽上的文本,再把文本轉(zhuǎn)化成語音即可。

    標簽朗讀

    這個很簡單了,只用根據(jù)當前是什么標簽,給出提示即可。

    // 標簽朗讀文本

    var tagTextConfig = {

    'a': '鏈接',

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

    'input[password]': '密碼輸入框',

    'button': '按鈕',

    'img': '圖片'

    };

    還有需要朗讀的標簽,繼續(xù)再添加即可。

    然后根據(jù)標簽,返回前綴文本即可。

    /**

    * 獲取標簽朗讀文本

    * @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;

    }

    // 標簽的功能提醒和作用應(yīng)該有間隔,因此在最后加入一個空格

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

    }

    獲取完整的朗讀文本就更簡單了,先取標簽的功能提醒,再取標簽的文本即可。

    文本內(nèi)容優(yōu)先取 title 其次 alt 最后 innerText。

    /**

    * 獲取完整朗讀文本

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

    * @returns {String} 朗讀文本

    */

    function getText(el) {

    if (!el) return '';

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

    }

    這樣就可以獲取到一個標簽的功能提醒和內(nèi)容的全部帶朗讀文本了。

    正文分隔

    接下來要處理的就是正文分隔了,在這個過程中,踩了不少坑,走了不少彎路,好好記錄一下。

    首先準備了正文分隔的配置:

    // 正文拆分配置

    var splitConfig = {

    // 內(nèi)容分段標簽名稱

    unitTag: 'p',

    // 正文中分隔正則表達式

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

    // 包裹標簽名

    wrapTag: 'label',

    // 包裹標簽類名

    wrapCls: 'speak-lable',

    // 高亮樣式名和樣式

    hightlightCls: 'speak-help-hightlight',

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

    };

    最開始想的就是直接按照正文中的分隔標點符號進行分隔就好了呀。

    想法如下:

    獲取段落全部文本

    使用 split(分隔正則表達式) 方法將正文按照標點符號分隔成小段

    每個小段用標簽包裹放回去即可

    然而理想很豐滿,現(xiàn)實很骨感。

    兩個大坑如下:

    split 方法進行分隔,分隔后分隔字符就丟了,也就是說把原文的一些標點符號給弄丟了。

    如果段落內(nèi)還存在其他標簽,而這個標簽內(nèi)部也正好存在待分隔的標點符號,那包裹分段標簽時直接破換了原標簽的完整性。

    關(guān)于第一個問題,丟失標點的符號,考慮過逐個標點來進行和替換 split 分隔方法為逐個字符循環(huán)來做。

    前者問題是原本一次完成的工作分成了多次,效率太低。第二種感覺效率更低了,分隔本來是很稀疏的,但是卻要變成逐個字符出判斷處理,更關(guān)鍵的是,分隔標點的位置要插入包裹標簽,會導(dǎo)致字符串長度變化,還要處理下標索引。代碼是機器跑的,或許不會覺得煩,但是我真的覺得好煩。如果這么干,或許以后哪個AI或者同事看到這樣的代碼,說不定會說“這真是個傻xxxx”。

    第二個問題想過很多辦法來補救,如先使用正則匹配捕獲內(nèi)容中成對的標簽,對標簽內(nèi)部的分隔先處理一遍,然后再處理整個的。

    想不明白問題二的,可參考一下待分隔的段落:

    這是一段測試文本,這里有個鏈接。您好,可以點擊此處進行跳轉(zhuǎn)還有其他內(nèi)容其他內(nèi)容容其他內(nèi)容容其他內(nèi)容,容其他內(nèi)容。

    如先使用/<((\w+?)>)(.+?)<\/\2(?=>)/g 正則,依次捕獲段落內(nèi)被標簽包裹的內(nèi)容,對標簽內(nèi)部的內(nèi)容先處理。

    但是問題又來了,這么處理的都是字符串,在js中都是基本類型,這些操作進行的時候都是在復(fù)制的基礎(chǔ)上進行的,要修改到原字符串里去,還得記錄下原本的開始結(jié)束位置,再將新的插進去。繁,還是繁,但是已經(jīng)比之前逐個字符去遍歷的好,正則捕獲中本來就有了匹配的索引,直接用即可,還能接受。

    但是這只是處理了段落內(nèi)部標簽的問題,段落內(nèi)肯定還有很多文本是沒有處理呢,怎么辦?

    正則匹配到了只是段落內(nèi)標簽的結(jié)果啊,外面的沒有啊。哦,對,有匹配到的索引,上次匹配到的位置加上上次處理的長度,就是一段直接文本的開始。下一次匹配到的索引-1就是這段直接文本的結(jié)束。這只是匹配過程中的,還有首尾要單獨處理。又回到煩的老路上去了。。。

    這么煩,一個段落分隔能這么繁瑣,我不信!

    突然想到了,有文本節(jié)點這么個東西,刪繁就簡嘛,正則先到邊上去,直接處理段落的所有節(jié)點不就行了。

    文本節(jié)點則分隔直接包裹,標簽節(jié)點則對內(nèi)容進行包裹,這種情況下處理的直接是dom,更省事。

    文本節(jié)點里放標簽?這是在開玩笑么,是也不是。文本節(jié)點里確實只能放文本,但是我把標簽直接放進去,它會自動轉(zhuǎn)義,那最后再替換出來不就行了。

    好了,方案終于有了,而且這個方案邏輯多簡單,代碼邏輯自然也不會煩。

    /**

    * 正文內(nèi)容分段處理

    * @param {jQueryObject/HTMLElement/String} $content 要處理的正文jQ對象或HTMLElement或其對應(yīng)選擇器

    */

    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 節(jié)點

    // 由于是文本節(jié)點,標簽被轉(zhuǎn)義了,后續(xù)再轉(zhuǎn)回來

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

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

    '';

    break;

    case 1:

    // 元素節(jié)點

    var innerHtml = node.innerHTML,

    start = '',

    end = '';

    // 如果內(nèi)部還有直接標簽,先去掉

    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);

    }

    // 更新內(nèi)部內(nèi)容

    node.innerHTML = start +

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

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

    '' +

    end;

    break;

    default:

    break;

    }

    });

    // 處理文本節(jié)點中被轉(zhuǎn)義的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);

    });

    }

    上面代碼中最后對文本節(jié)點中被轉(zhuǎn)義的包裹標簽替換似乎有點麻煩,但是沒辦法,ES5之前JavaScript并不支持正則的后行斷言(也就是正則表達式中“后顧”)。所以沒辦法對包裹標簽前后的 < 和 > 進行精準替換,只能連同標簽名一起替換。

    事件處理

    在上面完成了文本獲取和段落分隔,下面要做的就是鼠標移動上去時獲取文本觸發(fā)朗讀即可,移開時停止朗讀即可。

    鼠標移動,只讀一次,基于這兩點原因,使用 mouseenter 和 mouseleave 事件來完成。

    原因:

    不冒泡,不會觸發(fā)父元素的再次朗讀

    不重復(fù)觸發(fā),一個元素內(nèi)移動時不會重復(fù)觸發(fā)。

    /**

    * 在頁面上寫入高亮樣式

    */

    function createStyle() {

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

    var style = document.createElement('style');

    style.id = 'speak-light-style';

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

    document.getElementsByTagName('head')[0].appendChild(style);

    }

    // 非正文需要朗讀的標簽 逗號分隔

    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);

    // 排除段落內(nèi)的

    if ($target.parents('.' + splitConfig.wrapCls).length || $target.find('.' + splitConfig.wrapCls).length) {

    return;

    }

    // 圖片樣式單獨處理 其他樣式統(tǒng)一處理

    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();

    });

    // 段落內(nèi)文本朗讀

    $(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();

    });

    注意要把針對段落的語音處理和其他地方的分開。為什么? 因為段落是個塊級元素,鼠標移入段落中的空白時,如:段落前后空白、首行縮進、末行剩余空白等,是不應(yīng)該觸發(fā)朗讀的,如果不阻止掉,進行這些區(qū)域?qū)⒅苯佑|發(fā)整段文字的朗讀,失去了我們對段落文本內(nèi)分隔的意義,而且,無論什么方式轉(zhuǎn)化語音都是要時間的,大段內(nèi)容可能需要較長時間,影響語音輸出的體驗。

    文本合成語音

    上面我們是直接使用了 speakText(text) 和 stopSpeak() 兩個方法來觸發(fā)語音的朗讀和停止。

    我們來看下如何實現(xiàn)這個兩個功能。

    其實現(xiàn)代瀏覽器默認已經(jīng)提供了上面功能:

    var speechSU = new window.SpeechSynthesisUtterance();

    speechSU.text = '你好,世界!';

    window.speechSynthesis.speak(speechSU);

    復(fù)制到瀏覽器控制臺看看能不能聽到聲音呢?(需要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 : 單詞或句子邊界觸發(fā),即分隔處觸發(fā) Fired when the spoken utterance reaches a word or sentence boundary.

    onend : 結(jié)束時觸發(fā) Fired when the utterance has finished being spoken.

    onerror : 錯誤時觸發(fā) 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 : 暫停時觸發(fā) Fired when the utterance is paused part way through.

    onresume : 重新播放時觸發(fā) Fired when a paused utterance is resumed.

    onstart : 開始時觸發(fā) 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 : 聲音變化時觸發(fā)

    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() : 讀合成的語音,參數(shù)必須為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);

    }

    因為語音合成本來是個異步的操作,因此在過程中進行以上處理。

    現(xiàn)代瀏覽器已經(jīng)內(nèi)置了這個功能,兩個API接口兼容性如下:

    Feature

    Chrome

    Edge

    Firefox (Gecko)

    Internet Explorer

    Opera

    Safari

    (WebKit) Basic

    support 33

    (Yes)

    49 (49)

    No support

    ?

    7

    如果要兼容其他瀏覽器或者需要一種完美兼容的解決方案,可能就需要服務(wù)端完成了,根據(jù)給定文本,返回相應(yīng)語音即可,百度語音 就提供這樣的服務(wù)。

    申請創(chuàng)業(yè)報道,分享創(chuàng)業(yè)好點子。點擊此處,共同探討創(chuàng)業(yè)新機遇!

    相關(guān)標簽
    網(wǎng)頁開發(fā)

    相關(guān)文章

    熱門排行

    信息推薦