360前端星计划0411

前端工程化浅析

1.前言:什么是前端工程化

1.1目标

在前端领域,利用技术不断进步和经验逐步积累带来的各种方案,来解决在项目的开发、测试、维护阶段中遇到的种种低效和繁琐的问题。

1.2技术

工程化是一种思想,技术是一种实践。技术会随着时代进步不断地演进和改变,在不同时期,都会有不同地技术来承载和践行着工程化地思想。

1.3原因

前端工程化就是为了提效。这个提效体现在项目地开发、测试及维护阶段。
前端工程化的好处
规范化、模块化、组件化、自动化

2.规范化

规范化是项目可维护的基石

  • 版本管理及开发流程规范
  • 编写规范
    • 脚本
    • 样式
    • 目录结构

版本规范化的开发过程

git
版本管理/代码仓库
git flow

  • 基于git/简化了git的操作
  • 活动模型/行为规范
    git flow的开发流程如下图所示


    image.png

    流程如下:

git flow init //初始化一个项目
git branch //生成分支,一个master,一个develop
git checkout develop //切换到develop分支进行开发

切换完成后输入以下命令

git pull origin develop
//基于develop新建一个叫f1的
feature分支(是git checkout develop git checkout -b feature/f1的缩写)
git flow festure start f1
//开发完成后提交代码
git commit -am 'ADD#PRO-01#new func'
git push origin feature/f1
//将f1上新增的合并到develop上
git flow feature finish f1

开发完成后可以把develop上的内容合并到

git checkout master
git pull origin master
//先将develop分支上的内容放到release上,若这时候发现了错误可以进行修改,修改完成提交后会将修改同步到master和develop上
git checkout release/0.0.1
git flow release finish 0.0.1

当线上有一些紧急的bug时可以放到hotfix上去修改

git checkout master
git flow hotfix start fix1
//修改完成后用git finish 可以将修改保存到master和develop

3.模块化

一般将逻辑相关的代码放到同一个文件中,当作一个模块。
只需关注模块内逻辑的实现,无需考虑变量污染等问题,模块之间可互相调用。

3.1 CSS模块化解决方案

核心思想通过样式生效规则来避免冲突

scoped

它的原理就是给DOM节点添加data-v-version属性
.selector =>.selector[data-v-version]

CSS in JS

这个是一种思想。以脚本模块来写样式,甚至有封装好的样式模块可以直接使用。
样式 => 按规则生成的唯一selector

CSS MODULES

借助预编译使样式成为脚本中的变量
.selector => Object.selector|.selector => .main__sub__hash

BEM(Block__Element-Modifier)

按照规则,手写css,并在模板内增加相应class
优雅的使用BEM


image.png

Shadow DOM

为元素建宇shadow root ,使内部样式与外部样式完全隔离

3.2js模块化解决方案

有两个成熟的框架。一个是nodejs,带来了comminJs规范。
还有一个是从二手开始的Moudle-loader规范

4.组件化

组件化和模块化的核心思想都在于分治,实际带啦的好处就是团队协作效率和项目可维护性的提升
组件化开发时Web开发的趋势

4.1什么是组件

4.1.1UI为主

页面上的一个UI块可以封装成一个组件。比如页面的头部,封装成一个Header组件后,我希望它的脚本、样式和模板可以放在一个文件夹中,到时候便于维护。

4.1.2 逻辑为主

某一个功能逻辑也可以封装成一个组件。封装成一个组件后,我希望它的脚本、样式和模板可以放在一个文件夹中,可以一处封装,多处任意使用。
在Web前端领域,可以将由特定逻辑和UI进行的高内聚,低耦合的封装体称为一个组件。
侧重UI进行封装的组件:代码结构清晰,组件内的模块就近放置,方便进行修改和维护。这种组件具备高内聚,低耦合的特性,但普适性不高。
侧重逻辑进行封装的组件:除了具备上述优点外,还有很高的普适性,更方便组件重用
组件内可以包含组件:偏UI的组件往往都是包含有偏逻辑的组件。

5.自动化

核心思想:能由机器自动完成的事情,绝不让人来做。自动化是前端工程化的核心

  • 自动初始化eg.:vue-cli
  • 自动构建(打包)eg.:webpack
  • 自动测试 eg.:karma,jest
  • 自动部署eg.:Jenkins

5.1自动化测试

前端测试分类

这个图当中越往上与逻辑越不相关,越往下与逻辑越相关

5.2自动化部署

自动化部署方案

5.3自动化初始化

通过脚手架自动完成项目初始化,迅速搭建一个项目。

5.4自动化构建

工具有webpack、PARCEL

5.5自动化示例:360搜索专题页开发工具

这个工具的诉求如下:

自动化诉求

为实现上述需求,开发一个CLI,专门负责项目初始化和上线发布
配置一个支持多项目打包的webpack工程,满足预编译的需求
开发一个基于webpack4的插件,将静态资源上传至公司CDN
写一个基于Node.js的CLI
image.png

用以下命令捕获用户输入的参数和命令,并获得参数触发回调

const programe = require('commander')
program.on('--help',_=>{})
program.command('init').action((name,options) => {})

通过以下代码触发询问与用户交互

const inquirer = require('inquirer');
inquirer.prompt({
  type:'confirm',
name:'name',
message:'是否将产品发布至线上',
default:true
}).then(anser =>{})

通过以下代码帮助执行命令,例如发送HTTP请求

const child_process = require('child_process');
const HTTP = require('http');

增强交互效果

const chalk = require('chalk');
console.log(chalk.redBright('专题名称已被使用,请重新输入'));
const ora = require('ora');
const spinner = ora('正在加载中').start();
setTimeout(_ => {
    spinner.text = '加载完成';
    spinner.succeed();
},1000);

使用webpack4进行项目构建

image.png

webpack4核心参数配置

建议写法
  • 将不同环境的配置进行区分
  • 集成进来的工具的插件配置单独放置
  • evn配置使用.browserslistrc文件单独放置

前端动画还可以这样玩

1.JS动画的基本原理

1.定时器改变对象的属性
2.根据新的属性重新渲染动画

function update(context) {
   // 更新属性
}

const ticker = new Ticker();
ticker.tick(update, context);

动画的种类

1.JavaScript 动画
- 操作DOM
- Canvas
2.CSS 动画
- transition
- animation

  1. SVG 动画
    • SMIL

JS动画的优缺点

优点:
- 灵活度
- 可控性
- 性能
缺点:
- 易用性差

简单动画

通过以下代码实现小方块的旋转

let rotation = 0;
requestAnimationFrame(function update() {
  block.style.transform = `rotate(${rotation++}deg)`;
  requestAnimationFrame(update);
});

这样存在一个问题不能很好的精确控制速度

另一个版本

let rotation = 0;
let startTime = null;
const T = 2000;
requestAnimationFrame(function update() {
  if(!startTime) startTime = Date.now();
  const p = (Date.now() - startTime)/T;
  block.style.transform = `rotate(${360 * p}deg)`;
  requestAnimationFrame(update);
});

通用化

function update({target}, count) {
  target.style.transform = `rotate(${count++}deg)`;
}

class Ticker {
  tick(update, context) {
    let count = 0;
    requestAnimationFrame(function next() {
      if(update(context, ++count) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

const ticker = new Ticker();
ticker.tick(update, {target: block});

通用化2

既可以用target实现又可以用time实现

function update({target}, {time}) {
  target.style.transform = `rotate(${360 * time / 2000}deg)`;
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

const ticker = new Ticker();
ticker.tick(update, {target: block});

通用化3

function update({context}, {time}) {
  context.clearRect(0, 0, 512, 512);
  context.save();
  context.translate(100, 100);
  context.rotate(time * 0.005);
  context.fillStyle = '#00f';
  context.fillRect(-50, -50, 100, 100);
  context.restore();
}

class Ticker {
  tick(update, context) {
    let count = 0;
    let startTime = Date.now();
    requestAnimationFrame(function next() {
      count++;
      const time = Date.now() - startTime;
      if(update(context, {count, time}) !== false) {
        requestAnimationFrame(next);
      }
    });
  }
}

Timing

将上述封装成一个更强大的类

class Timing {
  constructor({duration, easing} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get p() {
    return this.easing(Math.min(this.time / this.duration, 1.0));
  }
}

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    requestAnimationFrame(function next() {
      count++;
      if(update(context, {count, timing}) !== false) {
        requestAnimationFrame(next);
      }
    });

匀速运动

实现2s内向右匀速运动200px

function update({target}, {timing}) {
  target.style.transform = `translate(${200 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, 
  {target: block}, 
  {duration: 2000}
);

自由落体运动实现

速度从0开始增加的一个加速运动

function update({target}, {timing}) {
  target.style.transform = `translate(0, ${200 * timing.p}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p ** 2,
});

摩擦力实现

把速度从一个开始的值减到0

function update({target}, {timing}) {
  target.style.transform = `translate(${200 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p * (2 - p),
});

平抛

把x轴和y轴的速度区分开

class Timing {
  constructor({duration, easing} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get op() {
    return Math.min(this.time / this.duration, 1.0);
  }
  get p() {
    return this.easing(this.op);
  }
}

function update({target}, {timing}) {
  target.style.transform = 
    `translate(${200 * timing.op}px, ${200 * timing.p}px)`;
}

旋转+平抛

function update({target}, {timing}) {
  target.style.transform = `
    translate(${200 * timing.op}px, ${200 * timing.p}px)
    rotate(${720 * timing.op}deg)
  `;
}

贝塞尔轨迹

function bezierPath(x1, y1, x2, y2, p) {
  const x = 3 * x1 * p * (1 - p) ** 2 + 3 * x2 * p ** 2 * (1 - p) + p ** 3;
  const y = 3 * y1 * p * (1 - p) ** 2 + 3 * y2 * p ** 2 * (1 - p) + p ** 3;
  return [x, y];
}

function update({target}, {timing}) {
  const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, timing.p);
  target.style.transform = `translate(${100 * px}px, ${100 * py}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: p => p * (2 - p),
});

bezier-easing

  • B(px) 作为输入, B(py) 作为输出
  • 通过牛顿迭代,从B(px)求p,从p求B(py)
function update({target}, {timing}) {
  target.style.transform = `translate(${100 * timing.p}px, 0)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});

bezier-easing 轨迹

function update({target}, {timing}) {
  target.style.transform =
    `translate(${100 * timing.p}px, ${100 * timing.op}px)`;
}

const ticker = new Ticker();
ticker.tick(update, {target: block}, {
  duration: 2000,
  easing: BezierEasing(0.5, -1.5, 0.5, 2.5),
});

椭圆轨迹

周期运动

class Timing {
  constructor({duration, easing, iterations = 1} = {}) {
    this.startTime = Date.now();
    this.duration = duration;
    this.easing = easing || function(p){return p};
    this.iterations = iterations;
  }
  get time() {
    return Date.now() - this.startTime;
  }
  get finished() {
    return this.time / this.duration >= 1.0 * this.iterations;
  }
  get op() {
    let op = Math.min(this.time / this.duration, 1.0 * this.iterations);
    if(op < 1.0) return op;
    op -= Math.floor(op);
    return op > 0 ? op : 1.0;
  }
  get p() {
    return this.easing(this.op);
  }
}

椭圆周期运动

小球转10周停止

function update({target}, {timing}) {
  const x = 150 * Math.cos(Math.PI * 2 * timing.p);
  const y = 100 * Math.sin(Math.PI * 2 * timing.p);
  target.style.transform = `
    translate(${x}px, ${y}px)
  `;
}

const ticker = new Ticker();
ticker.tick(update, {target: block},
  {duration: 2000, iterations: 10});

连续运动

返回一个promise,用await来逐步执行

class Ticker {
  tick(update, context, timing) {
    let count = 0;
    timing = new Timing(timing);
    return new Promise((resolve) => {
      requestAnimationFrame(function next() {
        count++;
        if(update(context, {count, timing}) !== false && !timing.finished) {
          requestAnimationFrame(next);
        } else {
          resolve(timing);
        }
      });      
    });
  }
}
function left({target}, {timing}) {
  target.style.left = `${100 + 200 * timing.p}px`;
}
function down({target}, {timing}) {
  target.style.top = `${100 + 200 * timing.p}px`;
}
function right({target}, {timing}) {
  target.style.left = `${300 - 200 * timing.p}px`;
}
function up({target}, {timing}) {
  target.style.top = `${300 - 200 * timing.p}px`;
}

(async function() {
  const ticker = new Ticker();
  await ticker.tick(left, {target: block},
    {duration: 2000});
  await ticker.tick(down, {target: block},
    {duration: 2000});
  await ticker.tick(right, {target: block},
    {duration: 2000});
  await ticker.tick(up, {target: block},
    {duration: 2000});
})();

线性插值(lerp)

function lerp(setter, from, to) {
  return function({target}, {timing}) {
    const p = timing.p;
    const value = {};
    for(let key in to) {
      value[key] = to[key] * p + from[key] * (1 - p);
    }
    setter(target, value);
  }
}

可以调用这个函数更方便的实现前面的功能

function setValue(target, value) {
  for(let key in value) {
    target.style[key] = `${value[key]}px`;
  }
}

const left = lerp(setValue, {left: 100}, {left: 300});
const down = lerp(setValue, {top: 100}, {top: 300});
const right = lerp(setValue, {left: 300}, {left: 100});
const up = lerp(setValue, {top: 300}, {top: 100});

(async function() {
  const ticker = new Ticker();
  await ticker.tick(left, {target: block},
    {duration: 2000});
  await ticker.tick(down, {target: block},
    {duration: 2000});
  await ticker.tick(right, {target: block},
    {duration: 2000});
  await ticker.tick(up, {target: block},
    {duration: 2000});
})();

弹跳的小球

const down = lerp(setValue, {top: 100}, {top: 300});
const up = lerp(setValue, {top: 300}, {top: 100});

(async function() {
  const ticker = new Ticker();
  
  // noprotect
  while(1) {
    await ticker.tick(down, {target: block},
      {duration: 2000, easing: p => p * p});
    await ticker.tick(up, {target: block},
      {duration: 2000, easing: p => p * (2 - p)});
  }
})();

弹跳的小球2

给弹跳加一个衰减

(async function() {
  const ticker = new Ticker();
  let damping = 0.7,
      duration = 2000,
      height = 300;

  // noprotect
  while(height >= 1) {
    let down = lerp(setValue, {top: 400 - height}, {top: 400});
    await ticker.tick(down, {target: block},
      {duration, easing: p => p * p});
    height *= damping ** 2;
    duration *= damping;
    let up = lerp(setValue, {top: 400}, {top: 400 - height});
    await ticker.tick(up, {target: block},
      {duration, easing: p => p * (2 - p)});
  }
})();

滚动

const roll = lerp((target, {left, rotate}) => {
    target.style.left = `${left}px`;
    target.style.transform = `rotate(${rotate}deg)`;
  },  
  {left: 100, rotate: 0}, 
  {left: 414, rotate: 720});


const ticker = new Ticker();

ticker.tick(roll, {target: block},
  {duration: 2000, easing: p => p});

平稳变速

function forward(target, {y}) {
  target.style.top = `${y}px`;
}

(async function() {
  const ticker = new Ticker();

  await ticker.tick(
    lerp(forward, {y: 100}, {y: 200}), 
    {target: block},
    {duration: 2000, easing: p => p * p}); 

  await ticker.tick(
    lerp(forward, {y: 200}, {y: 300}), 
    {target: block},
    {duration: 1000, easing: p => p}); 

  await ticker.tick(
    lerp(forward, {y: 300}, {y: 350}), 
    {target: block},
    {duration: 1000, easing: p => p * (2 - p)}); 
}());

甩球

function circle({target}, {timing}) {
  const p = timing.p;
  const rad = Math.PI * 2 * p;

  const x = 200 + 100 * Math.cos(rad);
  const y = 200 + 100 * Math.sin(rad);
  target.style.left = `${x}px`;
  target.style.top = `${y}px`;
}
function shoot({target}, {timing}) {
  const p = timing.p;
  const rad = Math.PI * 0.2;
  const startX = 200 + 100 * Math.cos(rad);
  const startY = 200 + 100 * Math.sin(rad);
  const vX = -100 * Math.PI * 2 * Math.sin(rad);
  const vY = 100 * Math.PI * 2 * Math.cos(rad);
  
  const x = startX + vX * p;
  const y = startY + vY * p;

  target.style.left = `${x}px`;
  target.style.top = `${y}px`;
}
(async function() {
  const ticker = new Ticker();

  await ticker.tick(circle, {target: block},
    {duration: 2000, easing: p => p, iterations: 2.1}); 
  await ticker.tick(shoot, {target: block},
    {duration: 2000});
}());

逐帧动画

使用background-position来改变图片位置
使用SetInterval()每隔一段时间换一次class

<style type="text/css">
.sprite {
  display:inline-block; 
  overflow:hidden; 
  background-repeat: no-repeat;
  background-image:url(https://p.ssl.qhimg.com/t01f265b6b6479fffc4.png);
}

.bird0 {width:86px; height:60px; background-position: -178px -2px}
.bird1 {width:86px; height:60px; background-position: -90px -2px}
.bird2 {width:86px; height:60px; background-position: -2px -2px}

 #bird{
   position: absolute;
   left: 100px;
   top: 100px;
   zoom: 0.5;
 }
</style>
<div id="bird" class="sprite bird1"></div>
<script type="text/javascript">
var i = 0;
setInterval(function(){
  bird.className = "sprite " + 'bird' + ((i++) % 3);
}, 1000/10);
</script>

Web Animation API(Working Draft)

传入关键帧,与CSS是对应的

element.animate(keyframes, options);
target.animate([
  {backgroundColor: '#00f', width: '100px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#0a0', width: '200px', height: '100px', borderRadius: '0'},
  {backgroundColor: '#f0f', width: '200px', height: '200px', borderRadius: '100px'},
], {
  duration: 5000,
  fill: 'forwards',
});

封装成promise,达到逐个小球运动的效果

function animate(target, keyframes, options) {
  const anim = target.animate(keyframes, options);
  return new Promise((resolve) => {
    anim.onfinish = function() {
      resolve(anim);
    }
  });
}

(async function() {
  await animate(ball1, [
    {top: '10px'},
    {top: '150px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });

  await animate(ball2, [
    {top: '200px'},
    {top: '350px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });
 
  await animate(ball3, [
    {top: '400px'},
    {top: '550px'},
  ], {
    duration: 2000,
    easing: 'ease-in-out',
    fill: 'forwards',
  });
}());

一起优化前端性能

原因
与用户体验相关,决定了用户去留。希望发现网站的性能瓶颈,从而提升用户体验

1.RAIL模型

1.1 RAIL模型的概念

它是一个以用户为中心的性能模型,将用户行为分为4个方面:

  • Response
  • Animation
  • ldle
  • Load
    每个网络应用都具有与其生命周期相关的4个方面,而这些方面以不同的方式影响着性能。
    它的内容有两个部分:
  • 目标
    是一种恒定性的指标,因为人类对外界的感知是恒定的
  • 指导意见
    是一些针对性能的评估标准。这些标准往往依赖当时的硬件等因素。
    延迟与用户反应:
    100ms 以内用户会感觉可以立即获得结果。
    超过1s用户注意力会离开他们正在执行的任务。

响应:50ms处理事件

目标

在100ms内响应用户输入

指导

  • 50ms内处理用户输入事件,确保100ms内反馈用户可视的响应
  • 对于开销大的任务可分隔任务处理,或放到worker进程中执行,避免影响用户交互
  • 处理时间超过50ms的操作,始终给予反馈(进度和活动指示器)

动画:10ms处理事件

目标

  • 10ms或更短时间内生成一帧
  • 视觉平滑

指导

  • 在动画这样的高压点,尽量不要处理逻辑。提高达到60fps的机会
  • 动画类型
    • 滚动
    • 视觉动画
    • 拖拽动画

空闲时间最大化

目标

最大化空闲时间以增加页面在100ms内响应用户输入的几率

指导

  • 利用空闲时间完成推迟的工作
  • 空闲时间期间用户交互优先级最高
    关键指标
    1.响应:在100ms内响应用户输入
    2.动画:动画或滚动时,10ms产生一帧
    3.空闲时间:主线程空闲时间最大化
    4.加载:在1000ms内呈现交互内容
    5.以用户为中心

2.工具篇

Lighthouse

可以选择是移动端还是客户端。它进行评估后会给出一些性能优化的建议

WebPageTest

是一个在线的网站

Chorme DEvTools

3.实战篇

3.1浏览器渲染场景

浏览器渲染场景

csstriggers.com可以查看每个属性影响的范围

3.2浏览器渲染流程

  • JS(实现动画,操作DOM)
  • Style(产出渲染树)
  • Layout(盒模型,确切的位置和大小)
  • Paint(栅格化,完整显示)
  • Composite(渲染层合并)


    性能面板
  • 在sources中可以查看代码的耗时情况
  • 优化方向:尽量不要在设置样式之后读取它的样式属性
  • 用transform属性来移动元素

3.3性能优化方向

  • 加载
    • 资源效率优化
    • 图片优化
    • 字体优化
    • 关键渲染路径优化
  • 渲染
    • JS执行优化
    • 避免大型复杂的布局
    • 渲染层合并
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容