物理學(xué)的 H5 應(yīng)用:模擬慣性滑動(dòng)

在移動(dòng)端 H5 中,時(shí)間選擇器(date-picker)、省市區(qū)選擇器(area-picker)等組件經(jīng)常會(huì)使用這樣的交互效果:

微信原生 date-picker 效果

這個(gè) gif 是在【微信錢包 - 賬單】中錄制的 ios 原生時(shí)間選擇器吏口。可見(jiàn),當(dāng)用戶手指在選擇器上先是滑動(dòng)再?gòu)钠聊簧弦崎_(kāi)蒲稳,內(nèi)容會(huì)繼續(xù)保持一段時(shí)間的滾動(dòng)效果瞭郑,并且滾動(dòng)的速度和持續(xù)的時(shí)間是與滑動(dòng)手勢(shì)的強(qiáng)烈程度成正比辜御。這種交互思路源于 ios 系統(tǒng)原生元素的滾動(dòng)回彈(momentum-based scrolling),來(lái)看 H5 的一個(gè)普通列表在 ios 上的滾動(dòng)表現(xiàn):

社區(qū)上大部分的移動(dòng)端組件庫(kù)的選擇器組件都采取了這種交互方式屈张,看看效果:

weui 的選擇器實(shí)現(xiàn)了慣性滑動(dòng)擒权,但滑動(dòng)動(dòng)畫結(jié)束得有點(diǎn)突兀,效果一般阁谆。

vant 的選擇器壓根沒(méi)有做慣性滑動(dòng)碳抄,當(dāng)手指從屏幕上移開(kāi)后,選擇器的滑動(dòng)會(huì)立刻停止场绿∑市В可見(jiàn)這樣的交互體驗(yàn)是比較差的。

接下來(lái)我會(huì)從設(shè)計(jì)層面剖析和模擬慣性滑動(dòng)的交互效果焰盗。

物理學(xué)應(yīng)用

不難想象璧尸,慣性滑動(dòng)非常貼合現(xiàn)實(shí)生活中的一些場(chǎng)景,如汽車剎車等熬拒。除此之外爷光,與物理力學(xué)中的滑塊模型也十分相似,由此我會(huì)參考滑塊模型來(lái)剖析慣性滑動(dòng)的全過(guò)程澎粟。

慣性 來(lái)源于物理學(xué)中的慣性定律(即 牛頓第一定律):一切物體在沒(méi)有受到力的作用的時(shí)候蛀序,運(yùn)動(dòng)狀態(tài)不會(huì)發(fā)生改變,物體所擁有的這種性質(zhì)就被稱為慣性活烙。我們不妨把慣性滑動(dòng)模擬成滑動(dòng)滑塊然后釋放的過(guò)程(以下討論中用戶滑動(dòng)的目標(biāo)皆模擬成 滑塊)徐裸,主要?jiǎng)澐譃閮蓚€(gè)階段:

  • 用戶滑動(dòng)滑塊使其從靜止開(kāi)始做加速運(yùn)動(dòng);
  • 用戶釋放滑塊使其只在摩擦力的作用下繼續(xù)滑動(dòng)啸盏,直至靜止倦逐;

慣性滑動(dòng)距離

描述滑塊的慣性滑動(dòng),首先需要求出滑動(dòng)的距離宫补。在上述二階段中檬姥,滑塊受摩擦力F_{摩}勻減速直線運(yùn)動(dòng)。假設(shè)滑動(dòng)距離為s_{2}粉怕,初速度為v_{0}健民,末速度為0m/s。根據(jù)位移公式

s_{2} = \frac{0 + v_{0}}{2}t_{2}

加速度公式

a = \frac{0 - v_{0}}{t_{2}}

可以算出慣性滑動(dòng)距離

s_{2} = - \frac{v_{0}^2 }{2a}

由于勻減速運(yùn)動(dòng)的加速度為負(fù)贫贝,不妨設(shè)一個(gè)加速度常量A秉犹,使其滿足A = -2a蛉谜,那么

s_{2} = \frac{v_{0}^2}{A}

這里A為正數(shù)。也就是說(shuō)崇堵,我們只需要求出初始速度即可型诚。

實(shí)際計(jì)算時(shí),v_{0}^2會(huì)導(dǎo)致計(jì)算出的慣性滑動(dòng)距離過(guò)大鸳劳,因此公式調(diào)整為s_{2} = \frac{v_{0}}{A}狰贯。

關(guān)注第一個(gè)階段,假設(shè)用戶滑動(dòng)滑塊的距離為s_{1}赏廓,滑動(dòng)的持續(xù)時(shí)間是t_{1}涵紊,那么二階段的初速度v_{0}可以根據(jù)位移公式求得

v_{0} = \frac{2s_{1}}{t_{1}}

綜上,求慣性滑動(dòng)的距離我們需要記錄用戶滑動(dòng)滑塊的 距離s_{1}持續(xù)時(shí)間t_{1}幔摸,并設(shè)置一個(gè)合理的 加速度常量A摸柄。

經(jīng)測(cè)試,加速度常量的合適值為 A=0.003既忆。

注意驱负,這里的距離和持續(xù)時(shí)間并不是用戶滑動(dòng)滑塊的總距離和時(shí)長(zhǎng),而是觸發(fā)慣性滑動(dòng)范圍內(nèi)的距離和時(shí)長(zhǎng)患雇,詳見(jiàn)【慣性滑動(dòng)的啟動(dòng)條件】电媳。

慣性滑動(dòng)速度曲線

針對(duì)二階段的勻減速直線運(yùn)動(dòng),時(shí)間段\Delta t產(chǎn)生的位移差\Delta s = at^2庆亡,其中a<0。也就是說(shuō)時(shí)間越往后捞稿,同等時(shí)間間距下通過(guò)的位移越來(lái)越小又谋,也就是動(dòng)畫的推進(jìn)速度越來(lái)越慢。

這與 CSS3 transition-timing-function 中的 ease-out 速度曲線相吻合娱局,ease-out (即 cubic-bezier(0, 0, .58, 1))的貝塞爾曲線為

上圖來(lái)自 在線繪制貝塞爾曲線網(wǎng)站彰亥。圖表中的縱坐標(biāo)是指 動(dòng)畫推進(jìn)的進(jìn)程;橫坐標(biāo)是指 時(shí)間衰齐;原點(diǎn)坐標(biāo)為 (0, 0)任斋,終點(diǎn)坐標(biāo)為 (1, 1),假設(shè)動(dòng)畫持續(xù)時(shí)間為2秒耻涛,(1, 1)坐標(biāo)點(diǎn)則代表離動(dòng)畫開(kāi)始2秒時(shí)動(dòng)畫執(zhí)行完畢(100%)废酷。根據(jù)圖表可以得出,時(shí)間越往后動(dòng)畫進(jìn)程的推進(jìn)速度越慢抹缕,符合勻減速直線運(yùn)動(dòng)的特性澈蟆。

然而這樣的速度曲線過(guò)于線性平滑,減速效果不明顯卓研。我們基于 ios 滾動(dòng)回彈的效果趴俘,調(diào)整貝塞爾曲線的參數(shù)為 cubic-bezier(.17, .89, .45, 1)睹簇。

回彈

滑塊滑動(dòng)不是無(wú)邊界的,我們來(lái)考慮這樣的場(chǎng)景:當(dāng)滑塊向下滑動(dòng)寥闪,其頂部正要接觸容器上邊界時(shí)速度還沒(méi)有降到0m/s太惠,此時(shí)如果讓滑塊瞬間停止運(yùn)動(dòng),這樣的交互效果是不理想的疲憋。

我們可以把上邊界想象成一條與滑塊緊密貼合的固定彈簧凿渊,當(dāng)滑塊到達(dá)臨界點(diǎn)而速度還沒(méi)有降到0m/s時(shí),滑塊會(huì)繼續(xù)滑動(dòng)并拉動(dòng)彈簧使其往下形變柜某,同時(shí)會(huì)受到彈簧的反拉力作減速運(yùn)動(dòng)(動(dòng)能轉(zhuǎn)化為內(nèi)能)嗽元;當(dāng)滑塊速度降為0m/s,此時(shí)彈簧的形變量最大喂击,由于彈性特質(zhì)彈簧會(huì)恢復(fù)原狀(內(nèi)能轉(zhuǎn)化成動(dòng)能)剂癌,從而拉動(dòng)滑塊反向運(yùn)動(dòng)

回彈過(guò)程也可以分為兩個(gè)階段:

  • 滑塊拉動(dòng)彈簧作變減速運(yùn)動(dòng)翰绊。此階段滑塊受摩擦力F_{摩}和越來(lái)越大的彈簧反拉力F_{彈}共同作用佩谷,加速度越來(lái)越大,所以速度降為0m/s的時(shí)間非常短监嗜;
  • 彈簧恢復(fù)原狀谐檀,拉動(dòng)滑塊作先變加速后變減速運(yùn)動(dòng)。此階段滑塊受到的摩擦力F_{摩}和越來(lái)越小的彈簧拉力F_{彈}相互抵消裁奇,剛開(kāi)始F_{彈}>F_{摩}桐猬,滑塊作加速度越來(lái)越小的變加速運(yùn)動(dòng);隨之F_{彈}<F_{摩}刽肠,滑塊作加速度越來(lái)越大的變減速運(yùn)動(dòng)溃肪,直至靜止。這里為了交互效果我們可以營(yíng)造一個(gè)理想狀態(tài):滑塊靜止時(shí)彈簧剛好恢復(fù)形變音五。

回彈距離

根據(jù)上述分析惫撰,回彈的第一階段作加速度越來(lái)越大的變減速直線運(yùn)動(dòng),設(shè)此階段的初速度為v_{1}躺涝,可以與v_{0}建立以下關(guān)系

l_{滑塊} = \frac{v_{1}^2 - v_{0}^2}{2a}

那么回彈距離為

S_{回彈}=\int_{0}^{t}v(t)dt

微積分都來(lái)了厨钻,簡(jiǎn)直沒(méi)法算好吧…

我們可以根據(jù)運(yùn)動(dòng)模型來(lái)簡(jiǎn)化S_{回彈}的計(jì)算,由于該階段的加速度大于 非回彈慣性滑動(dòng) 的加速度坚嗜,設(shè) 非回彈慣性滑動(dòng) 的總距離為S_{滑}夯膀,那么

S_{回彈}<S_{滑}-l_{滑塊}

所以可以設(shè)置一個(gè)合理的常量B,使其滿足

S_{回彈} = \frac{S_{滑}-l_{滑塊}}{B}

經(jīng)測(cè)試苍蔬,常量B的合理取值為 10棍郎。

回彈速度曲線

整個(gè)觸發(fā)回彈的慣性滑動(dòng)模型包括三個(gè)運(yùn)動(dòng)階段:

然而把 階段a 和 階段b 描繪成 CSS 動(dòng)畫是有一定復(fù)雜度和風(fēng)險(xiǎn)的:

  • 階段b 中的變減速運(yùn)動(dòng)難以描繪;
  • 兩個(gè)階段運(yùn)動(dòng)方向相同但動(dòng)畫速度曲線不連貫银室,容易造成用戶體驗(yàn)的斷層涂佃;

出于簡(jiǎn)化的考慮励翼,可以將 階段a、b 合并為一個(gè)運(yùn)動(dòng)階段:

對(duì)于合并后的 階段a 末段辜荠,由于反向加速度越來(lái)越大汽抚,因此滑塊減速的效率會(huì)比 非回彈慣性滑動(dòng) 同期更大,對(duì)應(yīng)的貝塞爾曲線末段也會(huì)更陡伯病,參數(shù)調(diào)整為 cubic-bezier(.25, .46, .45, .94)造烁。

在 階段b 中,滑塊先變加速后變減速午笛,嘗試 ease-in-out 的動(dòng)畫曲線:

可以看出惭蟋,由于 階段b 初始的 ease-in 曲線使 階段a、b 的銜接段稍有停留药磺,效果體驗(yàn)一般告组。所以我們選擇只描繪變減速運(yùn)動(dòng)這一段,調(diào)整貝塞爾曲線為 cubic-bezier(.165, .84, .44, 1)癌佩。

由于 mp4 轉(zhuǎn) gif 格式會(huì)掉幀木缝,所以示例效果看起來(lái)會(huì)有點(diǎn)卡頓,建議直接體驗(yàn) demo围辙。

動(dòng)畫時(shí)長(zhǎng)

PS:以下取值都是基于對(duì) ios 滾動(dòng)回彈實(shí)例的測(cè)量我碟。

一次慣性滑動(dòng)可能會(huì)出現(xiàn)兩種情況:

  • 沒(méi)有觸發(fā)回彈
    滑動(dòng)動(dòng)畫的持續(xù)時(shí)間為 2500ms

  • 觸發(fā)回彈
    階段a 中姚建,當(dāng)S_{回彈}大于某個(gè)閾值時(shí)矫俺,為 強(qiáng)回彈,動(dòng)畫時(shí)長(zhǎng)設(shè)為 400ms掸冤,反之為 弱回彈厘托,時(shí)長(zhǎng)設(shè)為 800ms
    階段b 持續(xù)時(shí)間為 500ms贩虾;

慣性滑動(dòng)啟停

  • 啟動(dòng)條件

慣性滑動(dòng)的啟動(dòng)需要有足夠的動(dòng)量。我們可以簡(jiǎn)單地認(rèn)為沥阱,當(dāng)用戶滑動(dòng)的距離足夠大(大于 15px)和持續(xù)時(shí)間足夠短(小于 300ms)時(shí)缎罢,即可產(chǎn)生慣性滑動(dòng)。也就是說(shuō)考杉,最后一次 touchmove 事件觸發(fā)的時(shí)間和 touchend 事件觸發(fā)的時(shí)間間隔小于 300ms策精,且兩者產(chǎn)生的距離差大于 15px 時(shí)認(rèn)為啟動(dòng)慣性滑動(dòng)。

  • 暫停時(shí)機(jī)

當(dāng)慣性滑動(dòng)未結(jié)束(包括處于回彈過(guò)程)崇棠,用戶再次觸碰滑塊時(shí)會(huì)暫脱释啵滑塊的運(yùn)動(dòng)。原理上是通過(guò) getComputedStylegetPropertyValue 方法獲取當(dāng)前的 transform: matrix() 矩陣值枕稀,抽離出水平 y 軸偏移量后重新調(diào)整 translate 的位置询刹。

完整代碼

demo 基于 vuejs 實(shí)現(xiàn)谜嫉,預(yù)覽地址:https://codepen.io/JunreyCen/pen/arRYem

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
    <style>
      body, ul {
        margin: 0;
        padding: 0;
      }
      ul {
        list-style: none;
      }
      .wrapper {
        position: absolute;
        top: 50%;
        left: 0;
        right: 0;
        margin: 0 auto;
        height: 80%;
        width: 80%;
        max-width: 300px;
        max-height: 500px;
        border: 1px solid #000;
        transform: translateY(-50%);
        overflow: hidden;
      }
      .list {
        background-color: #70f3b7;
      }
      .list-item {
        height: 40px;
        line-height: 40px;
        width: 100%;
        text-align: center;
        border-bottom: 1px solid #ccc;
      }
    </style>
  </head>
  <body>
    <div id="app"></div>
  
    <template id="tpl">
      <div
        class="wrapper"
        ref="wrapper"
        @touchstart.prevent="onStart"
        @touchmove.prevent="onMove"
        @touchend.prevent="onEnd"
        @touchcancel.prevent="onEnd"
        @mousedown.prevent="onStart"
        @mousemove.prevent="onMove"
        @mouseup.prevent="onEnd"
        @mousecancel.prevent="onEnd"
        @mouseleave.prevent="onEnd"
        @transitionend="onTransitionEnd">
        <ul
          class="list"
          ref="scroller"
          :style="scrollerStyle">
          <li 
            class="list-item"
            v-for="item in list">
            {{item}}
          </li>
        </ul>
      </div>
    </template>

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
      new Vue({
        el: '#app',
        template: '#tpl',
        computed: {
          list() {
            const list = [];
            for (let i = 0; i < 100; i++) {
              list.push(i);
            }
            return list;
          },
          scrollerStyle() {
            return {
              'transform': `translate3d(0, ${this.offsetY}px, 0)`,
              'transition-duration': `${this.duration}ms`,
              'transition-timing-function': this.bezier,
            };
          },
        },
        data() {
          return {
            wrapper: null,
            scroller: null,
            minY: 0,
            maxY: 0,
            wrapperHeight: 0,
            offsetY: 0,
            duration: 0,
            bezier: 'linear',
            startY: 0,
            pointY: 0,
            startTime: 0,                 // 慣性滑動(dòng)范圍內(nèi)的 startTime
            momentumStartY: 0,            // 慣性滑動(dòng)范圍內(nèi)的 startY
            momentumTimeThreshold: 300,   // 慣性滑動(dòng)的啟動(dòng) 時(shí)間閾值
            momentumYThreshold: 15,       // 慣性滑動(dòng)的啟動(dòng) 距離閾值
            isStarted: false,             // start鎖
          };
        },
        mounted() {
          this.$nextTick(() => {
            this.wrapper = this.$refs.wrapper;
            this.scroller = this.$refs.scroller;
            const { height: wrapperHeight } = this.wrapper.getBoundingClientRect();
            const { height: scrollHeight } = this.scroller.getBoundingClientRect();
            this.wrapperHeight = wrapperHeight;
            this.minY = wrapperHeight - scrollHeight;
          });
        },
        methods: {
          onStart(e) {
            const point = e.touches ? e.touches[0] : e;
            this.isStarted = true;
            this.duration = 0;
            this.stop();
            this.pointY = point.pageY;
            this.momentumStartY = this.startY = this.offsetY;
            this.startTime = new Date().getTime();
          },
          onMove(e) {
            if (!this.isStarted) return;
            const point = e.touches ? e.touches[0] : e;
            const deltaY = point.pageY - this.pointY;
            // 浮點(diǎn)數(shù)坐標(biāo)會(huì)影響渲染速度
            let offsetY = Math.round(this.startY + deltaY);
            // 超出邊界時(shí)增加阻力
            if (offsetY < this.minY || offsetY > this.maxY) {
              offsetY = Math.round(this.startY + deltaY / 3);
            }
            this.offsetY = offsetY;
            const now = new Date().getTime();
            // 記錄在觸發(fā)慣性滑動(dòng)條件下的偏移值和時(shí)間
            if (now - this.startTime > this.momentumTimeThreshold) {
              this.momentumStartY = this.offsetY;
              this.startTime = now;
            }
          },
          onEnd(e) {
            if (!this.isStarted) return;
            this.isStarted = false;
            if (this.isNeedReset()) return;
            const absDeltaY = Math.abs(this.offsetY - this.momentumStartY);
            const duration = new Date().getTime() - this.startTime;
            // 啟動(dòng)慣性滑動(dòng)
            if (duration < this.momentumTimeThreshold && absDeltaY > this.momentumYThreshold) {
              const momentum = this.momentum(this.offsetY, this.momentumStartY, duration);
              this.offsetY = Math.round(momentum.destination);
              this.duration = momentum.duration;
              this.bezier = momentum.bezier;
            }
          },
          onTransitionEnd() {
            this.isNeedReset();
          },
          momentum(current, start, duration) {
            const durationMap = {
              'noBounce': 2500,
              'weekBounce': 800,
              'strongBounce': 400,
            };
            const bezierMap = {
              'noBounce': 'cubic-bezier(.17, .89, .45, 1)',
              'weekBounce': 'cubic-bezier(.25, .46, .45, .94)',
              'strongBounce': 'cubic-bezier(.25, .46, .45, .94)',
            };
            let type = 'noBounce';
            // 慣性滑動(dòng)加速度
            const deceleration = 0.003;
            // 回彈阻力
            const bounceRate = 10;
            // 強(qiáng)弱回彈的分割值
            const bounceThreshold = 300;
            // 回彈的最大限度
            const maxOverflowY = this.wrapperHeight / 6;
            let overflowY;

            const distance = current - start;
            const speed = 2 * Math.abs(distance) / duration;
            let destination = current + speed / deceleration * (distance < 0 ? -1 : 1);
            if (destination < this.minY) {
              overflowY = this.minY - destination;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.max(this.minY - maxOverflowY, this.minY - overflowY / bounceRate);
            } else if (destination > this.maxY) {
              overflowY = destination - this.maxY;
              type = overflowY > bounceThreshold ? 'strongBounce' : 'weekBounce';
              destination = Math.min(this.maxY + maxOverflowY, this.maxY + overflowY / bounceRate);
            }

            return {
              destination,
              duration: durationMap[type],
              bezier: bezierMap[type],
            };
          },
          // 超出邊界時(shí)需要重置位置
          isNeedReset() {
            let offsetY;
            if (this.offsetY < this.minY) {
              offsetY = this.minY;
            } else if (this.offsetY > this.maxY) {
              offsetY = this.maxY;
            }
            if (typeof offsetY !== 'undefined') {
              this.offsetY = offsetY;
              this.duration = 500;
              this.bezier = 'cubic-bezier(.165, .84, .44, 1)';
              return true;
            }
            return false;
          },
          stop() {
            // 獲取當(dāng)前 translate 的位置
            const matrix = window.getComputedStyle(this.scroller).getPropertyValue('transform');
            this.offsetY = Math.round(+matrix.split(')')[0].split(', ')[5]);
          },
        },
      });
    </script>
  </body>
</html>

Reference

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凹联,隨后出現(xiàn)的幾起案子沐兰,更是在濱河造成了極大的恐慌,老刑警劉巖蔽挠,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件住闯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡澳淑,警方通過(guò)查閱死者的電腦和手機(jī)比原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)杠巡,“玉大人量窘,你說(shuō)我怎么就攤上這事『瞿酰” “怎么了绑改?”我有些...
    開(kāi)封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)兄一。 經(jīng)常有香客問(wèn)我厘线,道長(zhǎng),這世上最難降的妖魔是什么出革? 我笑而不...
    開(kāi)封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任造壮,我火速辦了婚禮,結(jié)果婚禮上骂束,老公的妹妹穿的比我還像新娘耳璧。我一直安慰自己,他們只是感情好展箱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布旨枯。 她就那樣靜靜地躺著,像睡著了一般混驰。 火紅的嫁衣襯著肌膚如雪攀隔。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天栖榨,我揣著相機(jī)與錄音昆汹,去河邊找鬼。 笑死婴栽,一個(gè)胖子當(dāng)著我的面吹牛满粗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愚争,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼映皆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挤聘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起劫扒,我...
    開(kāi)封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤檬洞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后沟饥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體添怔,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年贤旷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了广料。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幼驶,死狀恐怖艾杏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盅藻,我是刑警寧澤购桑,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站氏淑,受9級(jí)特大地震影響勃蜘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜假残,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一缭贡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辉懒,春花似錦阳惹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颠印,卻和暖如春纲岭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗽仪。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工荒勇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柒莉,地道東北人闻坚。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像兢孝,于是被迫代替她去往敵國(guó)和親窿凤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仅偎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345