利用 javascript 實現(xiàn)富文本編輯器

2017/11/09 · JavaScript · 富文本

原文出處: 百度EUX/田光宇

近期項目中需要開發(fā)一個兼容PC和移動端的富文本編輯器,其中包含了一些特殊的定制功能∫福考察了下現(xiàn)有的js富文本編輯器媳禁,桌面端的很多,移動端的幾乎沒有汪诉。桌面端以UEditor為代表抛蚁。但是我們并不打算考慮兼容性箱沦,所以沒有必要采用UEditor這么重的插件辩恼。為此決定自研一個富文本編輯器。本文谓形,主要介紹如何實現(xiàn)富文本編輯器灶伊,和解決一些不同瀏覽器和設備之間的bug。

準備階段

在現(xiàn)代瀏覽器中已經為我們準備好了許多API來讓 html 支持富文本編輯功能寒跳,我們沒有必要自己完成全部內容谁帕。

contenteditable=”true”

首先我們需要讓一個 div 成為可編輯狀態(tài),加入contenteditable="true" 屬性即可冯袍。

<div contenteditable="true" id="rich-editor"></div>

在這樣的 <div>中插入任何節(jié)點都將默認是可編輯狀態(tài)的匈挖。如果想插入不可編輯的節(jié)點,我們就需要指定插入節(jié)點的屬性為 contenteditable="false"康愤。

光標操作

作為富文本編輯器儡循,開發(fā)者需要有能力控制光標的各種狀態(tài)信息,位置信息等征冷。瀏覽器提供了 selection 對象和 range 對象來操作光標择膝。

selection 對象

Selection對象表示用戶選擇的文本范圍或插入符號的當前位置。它代表頁面中的文本選區(qū)检激,可能橫跨多個元素肴捉。文本選區(qū)由用戶拖拽鼠標經過文字而產生。
獲得一個 selection 對象

let selection = window.getSelection();

通常情況下我們不會直接操作 selection 對象叔收,而是需要操作用 seleciton 對象所對應的用戶選擇的 ranges (區(qū)域)齿穗,俗稱”拖藍“。獲取方式如下:

let range = selection.getRangeAt(0);

由于瀏覽器當前可能存在多個文本選取饺律,所以 getRangeAt 函數(shù)接受一個索引值窃页。在富文本編輯其中,我們不考慮多選取的可能性复濒。

selection 對象還有兩個重要的方法脖卖, addRangeremoveAllRanges。分別用于向當前選取添加一個 range 對象和 刪除所有 range 對象巧颈。之后你會看到他們的用途畦木。

range 對象

通過 selection 對象獲得的 range 對象才是我們操作光標的重點。Range表示包含節(jié)點和部分文本節(jié)點的文檔片段砸泛。初見 range 對象你有可能會感到陌生又熟悉十籍,在哪兒看見過呢蛆封?作為一個前端工程師,想必你一定拜讀過《javascript 高級程序設計第三版》 這本書妓雾。在第12.4節(jié)娶吞,作者為我們介紹了 DOM2 級提供的 range 接口垒迂,用來更好的控制頁面械姻。反正我當時看的一臉?机断?楷拳??這個有啥用吏奸,也沒有這種需求啊欢揖。這里我們就大量的用到這個對象。對于下面節(jié)點:

JavaScript
<div contenteditable="true" id="rich-editor">

<p>百度EUX團隊</p>

</div>

光標位置如圖所示:


image

打印出此時的 range 對象:


image

其中屬性含義如下:

  • startContainer: range 范圍的起始節(jié)點奋蔚。
  • endContainer: range 范圍的結束節(jié)點
  • startOffset: range 起點位置的偏移量她混。
  • endOffset: range 終點位置的偏移量。
  • commonAncestorContainer: 返回包含 startContainer 和 endContainer 的最深的節(jié)點泊碑。
  • collapsed: 返回一個用于判斷 Range 起始位置和終止位置是否相同的布爾值坤按。

這里我們的 startContainer , endContainer, commonAncestorContainer都為 #text 文本節(jié)點 ‘百度EUX團隊’。因為光標在‘度‘字后面馒过,所以startOffset 和 endOffset 均為 2臭脓。且沒有產生拖藍,所以 collapsed 的值為 true腹忽。我們再看一個產生拖藍的例子:

光標位置如圖所示:


image

打印出此時的 range 對象:


image

由于產生了拖藍 startContainer 和 endContainer 不再一致来累,collapsed 的值變?yōu)榱?false。startOffset 和 endOffset 正好代表了拖藍的起終位置窘奏。更多的效果大家自己嘗試吧嘹锁。

操作一個 range 節(jié)點,主要有如下方法:

  • setStart(): 設置 Range 的起點
  • setEnd(): 設置 Range 的終點
  • selectNode(): 設定一個包含節(jié)點和節(jié)點內容的 Range
  • collapse(): 向指定端點折疊該 Range
  • insertNode(): 在 Range 的起點處插入節(jié)點着裹。
  • cloneRange(): 返回擁有和原 Range 相同端點的克隆 Range 對象

富文本編輯里面常用的就這么多兼耀,還有很多方法就不列舉了。

修改光標位置

我們可以通過調用 setStart()setEnd() 方法求冷,來修改一個光標的位置或拖藍范圍瘤运。這兩個方法接受的參數(shù)為各自的起終節(jié)點和偏移量。例如我想讓光標位置到”百度EUX團隊”最末尾匠题,那么可以采用如下方法:

let range = window.getSelection().getRangeAt(0),

textEle = range.commonAncestorContainer;

range.setStart(range.startContainer, textEle.length);

range.setEnd(range.endContainer, textEle.length);

|

我們加入一個定時器來查看效果:

image

然而這種方式有個局限性拯坟,就是當光標所在的節(jié)點如果發(fā)生了變動。比如被替換或者加入新的節(jié)點了韭山,那么再用這種方式就不會有任何效果郁季。為此我們有時候需要一種強制更改光標位置手段, 簡要代碼如下(實際中你有可能還需要考慮自閉和元素等內容):

function resetRange(startContainer, startOffset, endContainer, endOffset) {

let selection  =  window.getSelection();

    selection.removeAllRanges();

let range  =  document.createRange();

range.setStart(startContainer,  startOffset);

range.setEnd(endContainer,  endOffset);

selection.addRange(range);

}

我們通過重新創(chuàng)造一個 range 對象并且刪除原有的 ranges 來保證光標一定會變動到我們想要的位置冷溃。

修改文本格式

實現(xiàn)富文本編輯器,我們就要能夠有修改文檔格式的能力梦裂,比如加粗似枕,斜體,文本顏色年柠,列表等內容凿歼。DOM 為可編輯區(qū)提供了 document.execCommand 方法,該方法允許運行命令來操縱可編輯區(qū)域的內容冗恨。大多數(shù)命令影響文檔的選擇(粗體答憔,斜體等),而其他命令插入新元素(添加鏈接)或影響整行(縮進)掀抹。當使用 contentEditable時虐拓,調用 execCommand() 將影響當前活動的可編輯元素。語法如下:

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)

  • aCommandName: 一個 DOMString 傲武,命令的名稱蓉驹。可用命令列表請參閱 命令 揪利。
  • aShowDefaultUI: 一個 Boolean态兴, 是否展示用戶界面,一般為 false土童。Mozilla 沒有實現(xiàn)诗茎。
  • aValueArgument: 一些命令(例如insertImage)需要額外的參數(shù)(insertImage需要提供插入image的url),默認為null献汗。

總之瀏覽器能把大部分我們想到的富文本編輯器需要的功能都實現(xiàn)了敢订,這里我就不一一演示了。感興趣的同學可以查看 MDN – document.execCommand罢吃。

到這里楚午,我相信你已經可以做出一個像模像樣的富文本編輯器了。想想還挺激動的尿招,但是呢矾柜,一切都沒有結束,瀏覽器又一次坑了我們就谜。

實戰(zhàn)開始怪蔑,填坑的旅途

就在我們都以為開發(fā)如此簡單的時候,實際上手卻遇到了許多坑丧荐。

修正瀏覽器的默認效果

瀏覽器提供的富文本效果并不總是好用的缆瓣,下面介紹幾個遇到的問題。

回車換行

當我們在編輯其中輸入內容并回車換行繼續(xù)輸入后虹统,可編輯框內容生成的節(jié)點和我們預期是不符的弓坞。


image
image

可以看到最先輸入的文字沒有被包裹起來隧甚,而換行產生的內容,包裹元素是 <div>標簽渡冻。為了能夠讓文字被 <p>元素包裹起來戚扳。
我們要在初始化的時候,向<div>默認插入 <p>
</p>元素
(
標簽用來占位族吻,有內容輸入后會自動刪除)帽借。這樣以后每次回車產生的新內容都會被 <p>元素包裹起來(在可編輯狀態(tài)下,回車換行產生的新結構會默認拷貝之前的內容呼奢,包裹節(jié)點宜雀,類名等各種內容)切平。
我們還需要監(jiān)聽 keyUp 事件下 event.keyCode === 8 刪除鍵握础。當編輯器中內容全被清空后(delete鍵也會把 <p>標簽刪除),要重新加入<p>
</p>標簽悴品,并把光標定位在里面禀综。

插入 ul 和 ol 位置錯誤

當我們調用 document.execCommand("insertUnorderedList", false, null) 來插入一個列表的時候,新的列表會被插入<p>標簽中苔严。

image

為此我們需要每次調用該命令前做一次修正定枷,參考代碼如下:

JavaScript

function  adjustList()  {

    let lists  =  document.querySelectorAll("ol, ul");

 for  (let  i  =  0;  i  <  lists.length;  i++)  {

        let ele  =  lists[i];  // ol

        let parentNode  =  ele.parentNode;

        if  (parentNode.tagName  ===  'P'  &&  parentNode.lastChild  ===  parentNode.firstChild)  {

                parentNode.insertAdjacentElement('beforebegin',  ele);

                parentNode.remove()

        }

    }

}

這里有個附帶的小問題,我試圖在 <li><p></p></li> 維護這樣的編輯器結構(默認是沒有 <p>標簽的)届氢。效果在 chrome 下運行很好欠窒。但是在 safari 中,回車永遠不會產生新的 <li>標簽退子,這樣就是去了該有的列表效果岖妄。

插入分割線

調用 document.execCommand('insertHorizontalRule', false, null); 會插入一個<hr>標簽。然而產生的效果卻是這樣的:

image

光標和<hr>的效果一致了寂祥。為此要判斷當前光標是否在 <li>里面荐虐,如果是則在 <hr>后面追加一個空的文本節(jié)點 #text 不是的話追加 <p><br></p>。然后將光標定位在里面丸凭,可用如下方式查找福扬。

/**

* 查找父元素

* @param {String} root

* @param {String | Array} name

*/

function  findParentByTagName(root,  name)  {

    let parent  =  root;

    if  (typeof name  ===  "string")  {

        name  =  [name];

    }

    while  (name.indexOf(parent.nodeName.toLowerCase())  ===  -1  &&  parent.nodeName  !==  "BODY"  &&  parent.nodeName  !==  "HTML")  {

        parent  =  parent.parentNode;

    }

    return  parent.nodeName  ===  "BODY"  ||  parent.nodeName  ===  "HTML"  ?  null  :  parent;

},

插入鏈接

調用 document.execCommand('createLink', false, url); 方法我們可以插入一個 url 鏈接,但是該方法不支持插入指定文字的鏈接惜犀。同時對已經有鏈接的位置可以反復插入新的鏈接铛碑。為此我們需要重寫此方法。

JavaScript

function  insertLink(url,  title)  {

    let selection  =  document.getSelection(),

        range  =  selection.getRangeAt(0);

    if(range.collapsed)  {

        let start  =  range.startContainer,

            parent  =  Util.findParentByTagName(start,  'a');

        if(parent)  {

            parent.setAttribute('src',  url);

        }else  {

            this.insertHTML(`<a  href="${url}">${title}</a>`);

        }

    }else  {

        document.execCommand('createLink',  false,  url);

    }

}

設置 h1 ~ h6 標題

瀏覽器沒有現(xiàn)成的方法虽界,但我們可以借助 document.execCommand('formatBlock', false, tag), 來實現(xiàn)汽烦,代碼如下:


function  setHeading(heading)  {

    let formatTag  =  heading,

        formatBlock  =  document.queryCommandValue("formatBlock");

    if  (formatBlock.length  >  0  &&  formatBlock.toLowerCase()  ===  formatTag)  {

        document.execCommand('formatBlock',  false,  ``);

    }  else  {

        document.execCommand('formatBlock',  false,  ``);

    }

}

插入定制內容

當編輯器上傳或加載附件的時候,要插入能夠展示附件的

節(jié)點卡片到編輯中浓恳。這里我們借助 document.execCommand('insertHTML', false, html); 來插入內容刹缝。為了防止div被編輯碗暗,要設置 contenteditable="false"哦。

處理 paste 粘貼

在富文本編輯器中梢夯,粘貼效果默認采用如下規(guī)則:

  1. 如果是帶有格式的文本言疗,則保留格式(格式會被轉換成html標簽的形式)
  2. 粘貼圖文混排的內容,圖片可以顯示颂砸,src 為圖片真實地址噪奄。
  3. 通過復制圖片來進行粘貼的時候,不能粘入內容
  4. 粘貼其他格式內容人乓,不能粘入內容

為了能夠控制粘貼的內容勤篮,我們監(jiān)聽 paste 事件。該事件的 event 對象中會包含一個 clipboardData 剪切板對象色罚。我們可以利用該對象的 getData 方法來獲得帶有格式和不帶格式的內容碰缔,如下。


let plainText  =  event.clipboardData.getData('text/plain');  // 無格式文本

let plainHTML  =  event.clipboardData.getData('text/html'); // 有格式文本

之后調用 document.execCommand('insertText', false, plainText);document.execCommand('insertHTML', false, plainHTML; 來重寫編輯上的paste效果戳护。

然而對于規(guī)則 3 金抡,上述方案就無法處理了。這里我們要引入 event.clipboardData.items腌且。這是一個數(shù)組包含了所有剪切板中的內容對象梗肝。比如你復制了一張圖片來粘貼,那么 event.clipboardData.items 的長度就為2:
items[0] 為圖片的名稱铺董,items[0].kind 為 ‘string’, items[0].type 為 ‘text/plain’ 或 ‘text/html’巫击。獲取內容方式如下:


items[0].getAsString(str  =>  {

    // 處理 str 即可

})

items[1] 為圖片的二進制數(shù)據(jù),items[1].kind 為’file’精续, items[1].type 為圖片的格式坝锰。想要獲取里面的內容,我們就需要創(chuàng)建 FileReader 對象了驻右。示例代碼如下:

let file  =  items[1].getAsFile();

// file.size 為文件大小

let reader  =  new  FileReader();

reader.onload  =  function()  {

    // reader.result 為文件內容什黑,就可以做上傳操作了

}

if(/image/.test(item.type))  {

    reader.readAsDataURL(file); // 讀取為 base64 格式

}

處理完圖片,那么對于復制粘貼其他格式內容會怎么樣呢堪夭?在 mac 中愕把,如果你復制一個磁盤文件,event.clipboardData.items 的長度為 2森爽。 items[0] 依然為文件名恨豁,然而 items[1] 則為圖片了,沒錯爬迟,是文件的縮略圖橘蜜。

輸入法處理

當使用輸入發(fā)的時候,有時候會發(fā)生一些意想不到的事情。 比如百度輸入法可以輸入一張本地圖片计福,為此我們需要監(jiān)聽輸入法產生的內容做處理跌捆。這里通過如下兩個事件處理:

  • compositionstart: 當瀏覽器有非直接的文字輸入時, compositionstart事件會以同步模式觸發(fā)
  • compositionend: 當瀏覽器是直接的文字輸入時, compositionend會以同步模式觸發(fā)

修復移動端的問題

在移動端,富文本編輯器的問題主要集中在光標和鍵盤上面象颖。我這里介紹幾個比較大的坑佩厚。

自動獲取焦點

如果想讓我們的編輯器自動獲得焦點,彈出軟鍵盤说订,可以利用 focus() 方法抄瓦。然而在 ios 下,死活沒有結果陶冷。這主要是因為 ios safari 中钙姊,為了安全考慮不允許代碼獲得焦點。只能通過用戶交互點擊才可以埂伦。還好煞额,這一限制可以去除:


[self.appWebView setKeyboardDisplayRequiresUserAction:NO]

iOS 下回車換行,滾動條不會自動滾動

在 iOS 下赤屋,當我們回車換行的時候立镶,滾動條并不會隨著滾動下去壁袄。這樣光標就可能被鍵盤擋住类早,體驗不好。為了解決這一問題嗜逻,我們就需要監(jiān)聽 selectionchange 事件涩僻,觸發(fā)時,計算每次光標編輯器頂端距離栈顷,之后再調用 window.scroll() 即可解決逆日。問題在于我們要如何計算當前光標的位置,如果僅是計算光標所在父元素的位置很有可能出現(xiàn)偏差(多行文本計算不準)萄凤。我們可以通過創(chuàng)建一個臨時 元素查到光標位置室抽,計算元素的位置即可。代碼如下:

function  getCaretYPosition()  {

    let sel  =  window.getSelection(),

        range  =  sel.getRangeAt(0);

    let span  =  document.createElement('span');

    range.collapse(false);

    range.insertNode(span);

    var  topPosition  =  span.offsetTop;

    span.parentNode.removeChild(span);

    return  topPosition;

}

正當我開心的時候靡努,安卓端反應坪圾,編輯器越編輯越卡。什么鬼惑朦?我在 chrome 上線檢查了一下兽泄,發(fā)現(xiàn) selectionchange 函數(shù)一直在運行,不管有沒有操作漾月。
在逐一排查的時候發(fā)現(xiàn)了這么一個事實病梢。range.insertNode 函數(shù)同樣觸發(fā) selectionchange 事件。這樣就形成了一個死循環(huán)梁肿。這個死循環(huán)在 safari 中就不會產生蜓陌,只出現(xiàn)在 safari 中觅彰,為此我們就需要加上瀏覽器類型判斷了。

鍵盤彈起遮擋輸入部分

網上對于這個問題主要的方案就是钮热,設置定時器缔莲。局限與前端,確實只能這采用這樣笨笨的解決霉旗。最后我們讓 iOS 同學在鍵盤彈出的時候痴奏,將 webview 高度減去軟鍵盤高度就解決了。


CGFloat webviewY  =  64.0  +  self.noteSourceView.height;

self.appWebView.frame  =  CGRectMake(0,  webviewY,  BDScreenWidth,  BDScreenHeight  -  webviewY  -  height);

插入圖片失敗

在移動端厌秒,通過調用 jsbridge 來喚起相冊選擇圖片读拆。之后調用 insertImage 函數(shù)來向編輯器插入圖片。然而鸵闪,插入圖片一直失敗檐晕。最后發(fā)現(xiàn)是因為早 safari 下,如果編輯器失去了焦點蚌讼,那么 selectionrange 對象將銷毀辟灰。因此調用 insertImage 時,并不能獲得光標所在位置篡石,因此失敗芥喇。為此需要增加,backupRange()restoreRange() 函數(shù)凰萨。當頁面失去焦點的時候記錄 range 信息继控,插入圖片前恢復 range 信息。

backupRange()  {

    let selection  =  window.getSelection();

    let range  =  selection.getRangeAt(0);

    this.currentSelection  =  {

        "startContainer":  range.startContainer,

        "startOffset":  range.startOffset,

        "endContainer":  range.endContainer,

        "endOffset":  range.endOffset

    }

}

restoreRange()  {

    if  (this.currentSelection)  {

        let selection  =  window.getSelection();

            selection.removeAllRanges();

        let range  =  document.createRange();

        range.setStart(this.currentSelection.startContainer,  this.currentSelection.startOffset);

        range.setEnd(this.currentSelection.endContainer,  this.currentSelection.endOffset);

        // 向選區(qū)中添加一個區(qū)域

        selection.addRange(range);

    }

}

在 chrome 中胖眷,失去焦點并不會清除 seleciton 對象和 range 對象武通,這樣我們輕輕松松一個 focus() 就搞定了。

重要問題就這么多珊搀,限于篇幅限制其他的問題省略了冶忱。總體來說境析,填坑花了開發(fā)的大部分時間囚枪。

其他功能

基礎功能修修補補以后,實際項目中有可能遇到一些其他的需求簿晓,比如當前光標所在文字內容狀態(tài)啊眶拉,圖片拖拽放大啊,待辦列表功能憔儿,附件卡片等功能啊忆植,markdown切換等等。在了解了js 富文本的種種坑之后,range 對象的操作之后朝刊,相信這些問題你都可以輕松解決耀里。這里最后提幾個做擴展功能時候遇到的有去的問題。

回車換行帶格式

前面已經說過了拾氓,富文本編輯器的機制就是這樣冯挎,當你回車換行的時候新產生的內容和之前的格式一模一樣。如果我們利用 .card 類來定義了一個卡片內容咙鞍,那么換行產生的新的段落都將含有 .card 類且結構也是直接 copy 過來的房官。我們想要屏蔽這種機制,于是嘗試在 keydown 的階段做處理(如果在 keyup 階段處理用戶體驗不好)续滋。然而翰守,并沒有什么用,因為用戶自定義的 keydown 事件要在 瀏覽器富文本的默認 keydown 事件之前觸發(fā)疲酌,這樣你就做不了任何處理蜡峰。
為此我們?yōu)檫@類特殊的個體都添加一個 property 屬性,添加在 property 上的內容是不會被copy下來的朗恳。這樣以后就可以區(qū)分出來了湿颅,從而做對應的處理。

獲取當前光標所在處樣式

這里主要是考慮 下劃線粥诫,刪除線之類的樣式油航,這些樣式都是用標簽類描述的,所以要遍歷標簽層級臀脏。直接上代碼:


function  getCaretStyle()  {

    let selection  =  window.getSelection(),

        range  =  selection.getRangeAt(0);

        aimEle  =  range.commonAncestorContainer,

        tempEle  =  null;

    let tags  =  ["U",  "I",  "B",  "STRIKE"],

        result  =  [];

    if(aimEle.nodeType  ===  3)  {

        aimEle  =  aimEle.parentNode;

    }

    tempEle  =  aimEle;

    while(block.indexOf(tempEle.nodeName.toLowerCase())  ===  -1)  {

        if(tags.indexOf(tempEle.nodeName)  !==  -1)  {

            result.push(tempEle.nodeName);

        }

        tempEle  =  tempEle.parentNode;

    }

    let viewStyle  =  {

        "italic":  result.indexOf("I")  !==  -1  ?  true  :  false,

        "underline":  result.indexOf("U")  !==  -1  ?  true  :  false,

        "bold":  result.indexOf("B")  !==  -1  ?  true  :  false,

        "strike":  result.indexOf("STRIKE")  !==  -1  ?  true  :  false

    }

    let styles  =  window.getComputedStyle(aimEle,  null);

    viewStyle.fontSize  =  styles["fontSize"],

    viewStyle.color  =  styles["color"],

    viewStyle.fontWeight  =  styles["fontWeight"],

    viewStyle.fontStyle  =  styles["fontStyle"],

    viewStyle.textDecoration  =  styles["textDecoration"];

    viewStyle.isH1  =  Util.findParentByTagName(aimEle,  "h1")  ?  true  :  false;

    viewStyle.isH2  =  Util.findParentByTagName(aimEle,  "h2")  ?  true  :  false;

    viewStyle.isP  =  Util.findParentByTagName(aimEle,  "p")  ?  true  :  false;

    viewStyle.isUl  =  Util.findParentByTagName(aimEle,  "ul")  ?  true  :  false;

    viewStyle.isOl  =  Util.findParentByTagName(aimEle,  "ol")  ?  true  :  false;

    return  viewStyle;

}

 |

# 最后說一句

該項目目前提測中劝堪,所以呢,一但發(fā)現(xiàn)有意思的坑揉稚,我會及時補充的。

# 參考內容

*   [MDN – document.execCommand](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/execCommand)
*   [MDN – selection](https://developer.mozilla.org/zh-CN/docs/Web/API/Selection)
*   [MDN – range](https://developer.mozilla.org/zh-CN/docs/Web/API/Range)
*   [input 事件兼容處理以及中文輸入法優(yōu)化](http://frontenddev.org/article/compatible-with-processing-and-chinese-input-method-to-optimize-the-input-events.html)
*   [js獲取剪切板內容熬粗,js控制圖片粘貼](https://segmentfault.com/a/1190000004288686)
*   [iOS UIWebView 全屬性詳解](http://blog.csdn.net/ll845876425/article/details/51884736)








最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末搀玖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驻呐,更是在濱河造成了極大的恐慌灌诅,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件含末,死亡現(xiàn)場離奇詭異猜拾,居然都是意外死亡,警方通過查閱死者的電腦和手機佣盒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門挎袜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事盯仪∥商拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵全景,是天一觀的道長耀石。 經常有香客問我,道長爸黄,這世上最難降的妖魔是什么滞伟? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮炕贵,結果婚禮上诗良,老公的妹妹穿的比我還像新娘。我一直安慰自己鲁驶,他們只是感情好鉴裹,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钥弯,像睡著了一般径荔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脆霎,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天总处,我揣著相機與錄音,去河邊找鬼睛蛛。 笑死鹦马,一個胖子當著我的面吹牛,可吹牛的內容都是我干的忆肾。 我是一名探鬼主播晦炊,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扒最!你這毒婦竟也來了赏殃?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤场仲,失蹤者是張志新(化名)和其女友劉穎和悦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渠缕,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡鸽素,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了亦鳞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馍忽。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡棒坏,死狀恐怖,靈堂內的尸體忽然破棺而出舵匾,到底是詐尸還是另有隱情俊抵,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布坐梯,位于F島的核電站徽诲,受9級特大地震影響,放射性物質發(fā)生泄漏吵血。R本人自食惡果不足惜谎替,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹋辅。 院中可真熱鬧钱贯,春花似錦、人聲如沸侦另。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褒傅。三九已至弃锐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殿托,已是汗流浹背霹菊。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留支竹,地道東北人旋廷。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像礼搁,于是被迫代替她去往敵國和親饶碘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容