canvas

canvas游戏设计

1.canvas基础知识

1.1canvas元素

  • canvas元素我们称之为画布,之所以称之为画布就像我们的白板一样,或者黑板一样,我们它只是一个容器,我们需要绘制的图形图像都需要绘制在这个黑板上,那其实我们知道canvas不光可以用来去绘制基本的图形或者图图案,同样我们也可以拿它去实现游戏。
  • 我们之所以不使用flash来完成游戏。是因为flash中。想要去运行,我们必须得要有对应的flash插件。没有对应的flash插件,我们没有办法在浏览器当中去运行我们要的游戏。而canvas对于我们来讲有个很方便的地方。就是他只是一个标签,只要你的浏览器支持canvas标签。我们就可以利用相关的代码来去完成一个游戏,或者图形回执。所以他是没有平台限制的并且也不需要我们去下载对应的插件。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
        <canvas id="can"></canvas>
    </body>
    </html>
    1. canvas 默认的宽高,300 X 150
    1. 给 canvas 元素设置宽高的时候,建议大家使用 canvas 标签本身提供的 width 和 height 属性。不建议大家使用 CSS 的样式设置,会导致 canvas 原本绘制的图片图形拉伸,变形。
    1. 如果浏览器不支持 canvas 标签,我们可以将提示的错误信息放在 canvas 标签的中间内容中
    // 1.获取canvas元素
    var can = document.getElementById('can');
    // 通过canvas提供给我们的 绘图环境(绘图上线文) 绘制相关的 图形或图案
    var ctx=can.getContext('2d');
    // 3.获取到用于绘制图形图案的 相关api

1.2canvas 的绘图环境

  • CanvasRenderingContext2D 对象提供的属性
属性 描述
canvas 绘图环境所属的 canvas 对象。
fillStyle 绘图环境在后续的图形填充操作中所使用的的颜色,渐变色或者图案
font 用来设置字形
lineCap 绘制的线段的端点的样式,butt,round,square
lineWidth 绘制的线条的宽度
shadowColor 用什么颜色绘制阴影
shadowOffsetX 水平方向的偏移
shadowOffsetY 垂直方向的偏移
strokeStyle 绘图环境在后续的图形描边操作中所使用的的颜色,渐变色或者图案
textAlign 本文的对齐方式
textBaseline 垂直的堆砌方式

1.3 直线

  • 对画直线的功能,我们可以使用beginPath(),closePath(),moveTo(),lineTo()和stroke()几个方法组合起来。

1.3.1 画直线

    // 获取对应的canvas 节点
    var can=document.getElementById('canvas');
    // 获取2d绘图环境
    var ctx=can.getContext('2d');
    // 开始一段新的路径
    ctx.beginPath();
    // 将笔触移动到某一点。
    ctx.moveTo(100,100);
    ctx.lineTo(200,200);
    // 绘制直线
    ctx.stroke();
  • beginPath() 定义一个新的路径绘制动作的开始
  • moveTo() 为固定点创建一个新的子路径,新的笔触点。
  • lineTo() 以上下文点为起点,到方法参数中指定的点之间画一条直线
  • stroke() 为所花的先赋予颜色等样式,并使其可见。如果没有特别指定颜色的话,则默认使用黑色。

1.3.2 直线的宽度

  • 直线的宽度用lineWidth属性设定。
    ctx.lineWidth=5;  //默认的按像素单位。

1.3.3 线条颜色

直线的颜色用strokeStyle属性

    ctx.strokeStyle='red'; 
    ctx.strokeStyle='rgba(0,0,0)'; 
    ctx.strokeStyle='#f00'; 

1.3.4 直线端点的样式

  • canvas支持3中直线的端点样式:butt,round,square. 使用lineCap属性设定。
    ctx.lineCap="butt";
    ctx.lineCap="round";
    ctx.lineCap="square";
    var canvas=document.getElementById('canvas');
    var ctx=canvas.getContext('2d');
    // butt  黄色  10
    ctx.beginPath();
    ctx.strokeStyle='yellow';
    ctx.lineWidth=10;
    ctx.lineCap='butt';
    ctx.moveTo(100,100);
    ctx.lineTo(300,100);
    ctx.stroke();
    // round  蓝色  20

    ctx.beginPath();
    ctx.strokeStyle='blue';
    ctx.lineWidth=20;
    ctx.lineCap='round';
    ctx.moveTo(100,130);
    ctx.lineTo(300,130);
    ctx.stroke();
    // square  红色  13

    ctx.beginPath();
    ctx.strokeStyle='red';
    ctx.lineWidth=15;
    ctx.lineCap='square';
    ctx.moveTo(100,160);
    ctx.lineTo(300,160);
    ctx.stroke();

1.4圆弧

1.4.1画弧线

  • 画弧线的方法是arc().每一条弧线都要由中心点,半径,起始角度(弧度制---弧度=角度值 * Math.PI/180),结束角度(弧度制---弧度=角度值 * Math.PI/180),后台绘图方向这几个参数来去确定一条弧线
    ctx.arc(x, y, radius, startAngle, endAngle, direction);
    // direction 用来设置圆弧的绘制方向
    // 默认  false  顺时针绘制
    //      true  逆时针绘制
  • 另外,也可以用 arcTo()方法来画弧线,用来在路径中绘制圆角。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>
    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        ctx.arc(300, 300, 150, (90 * Math.PI) / 180, (270 * Math.PI) / 180);
        ctx.stroke();
    </script>
    </html>

1.4.2二次曲线(二次贝塞尔曲线)

  • 通过 quadraticCurveTo()来完成二次曲线的绘制,每一条二次曲线要求要有上下文点,一个控制点和一个终止点来去定义。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        ctx.moveTo(100,100);
        //二次曲线的起点
        ctx.quadraticCurveTo(288,0,388,150);
        // arg1   控制点的x坐标
        // arg2   控制点的y坐标
        // arg3  结束点的x坐标
        // arg4  结束点的y坐标

        ctx.stroke();
    </script>
    </html>

1.4.3贝塞尔曲线

  • 使用bezierCurveTo(); 每一条贝塞尔曲线需要由起点,两个控制点和一个终止点来确定。由于贝塞尔曲线的是有两个控制点的,因此贝塞尔曲线可以比二次曲线更加复杂
    ctx.bezierCurveTo(control1X,control1Y,control2X,control2Y,endx,endy);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        ctx.beginPath();
        ctx.fillStyle="red";
        ctx.moveTo(100,300);
        ctx.bezierCurveTo(0,0,600,0,400,300);
        ctx.lineTo(400,300);
        ctx.arc(400,400,150,0,1.3*Math.PI);
        // ctx.stroke();
        ctx.fill();
    </script>

    </html>

1.4.4线条的链接样式

  • HTML5 canvas 中支持3种线条的链接样式;包括:miter,round和bevel,设定链接样式使用lineJoin属性,默认情况下使用miter样式
    ctx.lineJion='round';
  • 注意:如果线条比较细他们之间链接并不形成很尖锐的角度的话,那么不同的样式可能会比较难以区分
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        // 设置线条的宽度
        ctx.lineWidth = 30;
        // miter样式
        ctx.beginPath();
        ctx.moveTo(100, 150);
        ctx.lineTo(150, 50);
        ctx.lineTo(200, 150);
        ctx.lineJoin = "miter";
        ctx.stroke();
        // round样式
        ctx.beginPath();
        ctx.moveTo(240, 150);
        ctx.lineTo(290, 50);
        ctx.lineTo(340, 150);
        ctx.lineJoin = "round";
        ctx.stroke();

        // bevel样式
        ctx.beginPath();
        ctx.moveTo(380, 150);
        ctx.lineTo(430, 50);
        ctx.lineTo(480, 150);
        ctx.lineJoin = "bevel";
        ctx.stroke();
    </script>

    </html>

1.4.5圆角

  • 画圆角使用arcTo()方法,这个方法需要一个控制点,一个终止点,半径作为必要参数
    ctx.arcTo(controlX, controlY, endX, endY, radius);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        //  设置矩形相关的参数
        var rectWidth=200;
        var rectHeight=100;
        var rectX=200;
        var rectY=50;
        var radius=50;
        
        ctx.beginPath();
        ctx.moveTo(rectX,rectY);
        ctx.lineTo(rectX+rectWidth-radius,rectY);
        ctx.arcTo(rectX+rectWidth,rectY,rectX+rectWidth,rectY+radius,radius);
        ctx.lineTo(rectX+rectWidth,rectY+rectHeight);
        ctx.stroke();

    </script>

    </html>

1.5图形

1.5.1 自定义图形

  • 自定义图形,需要创建一个路径,然后closePath()方法闭合此路径

1.5.2 图形的颜色填充

  • 要填充图形,需要使用fillStyle属性设置填充图形的这个颜色,然后使用fillStyle方法完成图形的填充。默认情况下,fillStyle属性的这个颜色是黑色。
    ctx.fillStyle = "color";
    ctx.fill();
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        // 开始自定义图形
        ctx.beginPath();
        ctx.moveTo(170, 80);
        ctx.bezierCurveTo(130, 100, 130, 150, 230, 150);
        ctx.bezierCurveTo(250, 180, 320, 180, 340, 150);
        ctx.bezierCurveTo(420, 150, 420, 130, 390, 100);
        ctx.bezierCurveTo(430, 40, 370, 30, 340, 50);
        ctx.bezierCurveTo(320, 5, 250, 20, 250, 50);
        ctx.bezierCurveTo(200, 5, 220, 20, 150, 50);
        ctx.closePath();
        ctx.strokeStyle = 'green';
        ctx.stroke();
        ctx.fillStyle = '#999';
        ctx.fill();
    </script>

    </html>

1.5.3 矩形

  • 绘制矩形使用rect()方法,每一个矩形需要有左上角(x,y)的这个和矩形的(w,h)宽高来确定。
    ctx.rect(x,y,w,h);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        // 开始自定义图形
        ctx.beginPath();
        ctx.rect(200, 200, 200, 200)
        ctx.strokeStyle = 'green';
        ctx.lineWidth = 2;
        ctx.stroke();
        ctx.fillStyle = '#999';
        ctx.fill();
    </script>

    </html>

1.5.4 圆

  • 画圆只需要调用arc()方法的时候,将起始角度设为零,终止角度设为2*Math.PI就可以
    ctx.arc(x,y,radius,0,2*Math.PI,false)

1.5.5 半圆

  • 画半圆也是调用arc()方法,将起始角度设为零,终止角度设为1*Math.PI就可以
    ctx.arc(x,y,radius,0,1*Math.PI,false)

1.6 填充类型

1.6.1线性渐变

  • createLinearGradient() 方法从上下文对象中创建线性渐变对象。四个参数确定一条虚拟线段,渐变沿着这条线段的 方向。
  • 然后用addColorStop方法为线性渐变对象设置渐变线上的关键点的颜色。offset表示关键点实在渐变虚拟线段的什么位置,offset的取值范围是0-1之间,0表示起始点,1表示终止点。
   <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        //   开始自定义图形
        ctx.beginPath();
        ctx.moveTo(170,80);
        ctx.bezierCurveTo(130,100,130,150,230,150);
        ctx.bezierCurveTo(250,180,320,180,340,150);
        ctx.bezierCurveTo(420,150,420,130,390,100);
        ctx.bezierCurveTo(430,40,370,30,340,50);
        ctx.bezierCurveTo(320,5,250,20,250,50);
        ctx.bezierCurveTo(200,5,150,20,170,80);
        ctx.closePath();
        // 创建线性渐变对象
        var grd=ctx.createLinearGradient(250,0,370,200);
        // 添加渐变段 起点处颜色
        grd.addColorStop(0,"#f00");
        grd.addColorStop(0.2,"green");
        grd.addColorStop(0.4,"orange");
        // 添加渐变段 起点处颜色
        grd.addColorStop(1,"yellow");
        ctx.fillStyle=grd;
        ctx.fill();
        ctx.strokeStyle="green";
        ctx.stroke();
    </script>

    </html> 

1.6.2径性渐变

  • createRadialGradient() 方法创建径向渐变对象,参数是渐变的起始圆和终止圆。
    var grd = ctx.createRadialGradient(
    startX,
    startY,
    startRadius,
    endX,
    endY,
    endRadius
    );
    grd.addColorStop(offset, color);
    ctx.fillStyle = grd;
    ctx.fill();
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        //   开始自定义图形
        ctx.beginPath();
        ctx.arc(300, 300, 200, 0, 2 * Math.PI);
        ctx.closePath();
        // 创建线性渐变对象
        var grd = ctx.createRadialGradient(300, 300, 110, 300, 300, 200);
        // 添加渐变段 起点处颜色
        grd.addColorStop(0, "blue");
        grd.addColorStop(0.5, "skyblue");
        grd.addColorStop(0.8, "green");
        ctx.fillStyle = grd;
        ctx.fill();
        ctx.strokeStyle = "green";
        ctx.stroke();
    </script>
    </html>

1.6.3 图案填充

  • 图案填充和图案绘制不一样,我们要使用 canvas 填充图案,首先要使用 createParttern 的方法这个方法需要两个参数,第一个参数是图像对象,第二个参数是重复的这个模式。比如说 repeat,repeat-x repeat-y 以及 no-repeat。
    var pattern = ctx.createPattern(imgObj, repeatOption);
    // repeatOption 支持repeat,no-repeat,repeat-x,repeat-y
    ctx.fillStyle = pattern;
    ctx.fill();
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1600" height="1600"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        //  图案填充
        var imgObj = new Image();
        imgObj.src = "imgs/0.jpg";
        imgObj.onload = function() {
        var pattern = ctx.createPattern(imgObj, "no-repeat");
        // ctx.rect(0,0,1500,1500);
        ctx.arc(800, 800, 800, 0, 2 * Math.PI);
        ctx.fillStyle = pattern;
        ctx.fill();
        };
    </script>
    </html>

1.7 图像

1.7.1 绘制图像

  • 绘制图像需要使用 drawImage()方法。这个方法需要一个图片对象和一个起始点的坐标作为参数形式点的坐标是相对于 canvas 的左上角的位置
    ctx.drawImage(imgObj, x, y);

1.7.2 图像尺寸

    // w,h 指定绘制的图片的 宽高
    ctx.drawImage(imgObj, x, y, w, h);

1.7.3 图像裁剪

    ctx.drawImage(
    imgaeObj,
    sourceX,
    sourceY,
    sourceW,
    sourceH,
    destX,
    destY,
    destW,
    destH
    );
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");
        //  图像绘制
        var imgObj = new Image();
        imgObj.src = "imgs/1.jpeg";
        imgObj.onload = function() {
        ctx.drawImage(this, 0, 0);
        // debugger
        ctx.drawImage(this, 554, 0, 100, 100 / (this.width / this.height));
        ctx.drawImage(this, 74, 312, 330, 480, 554, 200, 330, 480);
        };
    </script>
    </html>

1.7.4 图像加载器

  • 使用多副图像的时候,最好是在绘制图像之前就把图像全部加载进来。
  • 我给大家提供一个简单的方法。就是用一个图像加载函数,一次性把所有的图像全部加在今这个图像对象当中去,然后再调用一个用户自定义的函数。在图像加载完成之后调用该函数。
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
        canvas {
            border: 3px solid red;
        }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        //   图片资源加载器
        function loadImages(sources, callback) {
        var images = {};
        var loadedImages = 0;
        var numImages = 0;
        // 获取图片的数量
        for (var i in sources) {
            numImages++;
        }
        for (var i in sources) {
            images[i] = new Image();
            images[i].src = sources[i];
            images[i].onload = function() {
            loadedImages++;
            if (loadedImages == numImages) {
                callback(images);
            }
            };
        }
        }
        var sources = { p: "imgs/1.jpeg", p2: "imgs/2.jpeg", p3: "imgs/3.jpeg" };

        loadImages(sources, function(images) {
        ctx.drawImage(images.p2, 0, 0);
        ctx.drawImage(images.p3, 100, 100);
        });
    </script>
    </html>

1.8 文字

1.8.1 文本的字体,大小和样式

  • 要设置字体大小和样式,需要用到上下文绘图环境对象的font属性。样式可以通过normal,italic或者bold。默认情况下是bold。
    ctx.font="italic 40px 微软雅黑";
    ctx.strokeStyle='red';
    ctx.fillStyle='red';

1.8.2 填充文本

    ctx.fillText(textContent,startX,startY);
    ctx.fillText(textContent,startX,startY,textW);

1.8.3 描绘文本边缘

    ctx.strokeText(textContent,startX,startY);
    ctx.strokeText(textContent,startX,startY,textW);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        ctx.font="bold 40px 微软雅黑";
        ctx.fillStyle='blue';
        ctx.strokeStyle="red";
        ctx.fillText('你好,范俊!',100,100);
        ctx.strokeText('你好,范俊!',100,100);
        ctx.strokeText('你好,范俊!',200,200);
        ctx.fillText('你好,范俊!',200,200);
    </script>

    </html>

1.8.4 文本对齐

  • 文本的对齐功能。我们使用textAlign属性。他可以存在的属性只有这么几个:start,end,left,center,right.对齐的位置是相对于一条虚拟的垂直线,这条线是由fillText或者这个strokeText方法定义的文本的X位置来去决定的。
  • 文本左对齐:
    • 1.textAlign 属性 left
    • 2.textAlign 属性 start,ltr(left toright)
    • 3.textAlign 属性 end ,rtl(right to left)
  • 文本右对齐:
    • 1.textAlign 属性 right
    • 2.textAlign 属性 start rtl(right to left)
    • 3.textAlign 属性 end ltr(left to right)

1.8.5 文本基线

  • 垂直对齐文本需要使用textBaseline属性。这个属性的可用值一共有这么几个:Top,hanging,middle,alphabetic,ideographic ,bottom。默认是alphabetic。
描述
alphabetic 默认。文本基线是普通的字母基线。
top 文本基线是 em 方框的顶端。
hanging 文本基线是悬挂基线。
middle 文本基线是 em 方框的正中。
ideographic 文本基线是表意基线。
bottom 文本基线是 em 方框的底端。

2.canvas 高级进阶

2.1 组合

2.1.1 阴影

  • 要为图形添加阴影,需要使用shadowColor,shadowBlur,shadowOffsetX和shadowOffsetY属性。
    ctx.shadowColor="black";
    ctx.shadowBlur=20;
    ctx.shadowOffsetX=10;
    ctx.shadowOffsetY=10;
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        ctx.fillStyle="red";
        ctx.shadowColor="black";
        ctx.shadowBlur=20;
        ctx.shadowOffsetX=-10;
        ctx.shadowOffsetY=-10;
    ctx.fillRect(50,50,200,100);
    </script>

    </html>

2.1.2 透明

  • 设置图形透明 globalAlpha属性。 属性值是介于0-1之间的 浮点数。0 表示完全透明,1表示完全不透明
    ctx.globalAlpha = 0.5;

2.1.3 裁剪区

创建裁剪区的方法是先绘制一个路径,然后使用clip()方法。

  • save(); 的作用是在创建裁剪区之前将canvas的当前状态保存起来,以便于在我们需要的时候,恢复当前状态
  • restore(); 恢复之前保存的状态
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        var x=100,y=100,r=80;
        var offset=60;

    // save();  的作用是在创建裁剪区之前将canvas的当前状态保存起来,以便于在我们需要的时候,恢复当前状态
    // restore(); 恢复之前保存的状态
        ctx.save();

        ctx.beginPath();
        ctx.arc(x,y,r,0,2*Math.PI,false);
        // ctx.fill();

        ctx.clip();

        ctx.beginPath();
        ctx.fillStyle='blue';
        ctx.arc(x-offset,y-offset,r,0,2*Math.PI,false);
        ctx.fill();

        ctx.beginPath();
        ctx.fillStyle='yellow';
        ctx.arc(x+offset,y-offset,r,0,2*Math.PI,false);
        ctx.fill();


        ctx.beginPath();
        ctx.fillStyle='red';
        ctx.arc(x,y+offset-30,r,0,2*Math.PI,false);
        ctx.fill();

        ctx.restore();
        ctx.beginPath();
        ctx.fillStyle='pink';
        ctx.fillRect(100,100,200,100);
    </script>

    </html>

2.1.4全局组合操作

  • 上下文对象的globalCompositeOperation属性定义了组合操作的方式,
  • 共有12种组合操作可供我们使用。包括: source-atop, source-in, sourceout,source-over, destination-atop, destination-in, destination-out,destination-over, lighter, xor, 和 copy 。默认状态下是 source-over 。
描述
source-over 默认。在目标图像上显示源图像。
source-atop 在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in 在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out 在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over 在源图像上方显示目标图像。
destination-atop 在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in 在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out 在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter 显示源图像 + 目标图像。
copy 显示源图像。忽略目标图像。
xor 使用异或操作对源图像与目标图像进行组合。
    ctx.globalCompositeOperation='source-atop'
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
        <canvas id="tempCanvas" width="1000" height="1000"
            style="position: fixed;left: 10px; top: 10px;border:3px solid green;display: none;"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');

        //   创建一个临时canvas  可以理解为内存中绘图所用。
        var tempCanvas = document.getElementById('tempCanvas');
        var tempCtx = tempCanvas.getContext('2d');

        var rectW = 50;
        var circleRadius = 30;
        var startx = 10;
        var starty = 10;

        var arr = ['source-atop', 'source-in', 'source-out', 'source-over', 'destination-atop', 'destination-in', 'destination-out', 'destination-over', 'lighter', 'xor', 'copy'];


        for (var i in arr) {
            var thisX = startx;
            var thisY = starty;
            if (i < 4) {
                // 第一行
                thisX = startx + i * 90;
                thisY = starty;
            } else if (i < 8) {
                // 第二行
                thisX = startx + (i - 4) * 90;
                thisY = starty + 100;
            } else {
                // 第三行
                thisX = startx + (i - 8) * 90;
                thisY = starty + 200;
            }
            tempCtx.clearRect(0, 0, tempCanvas.width, canvas.height);
            // 绘制矩形
            tempCtx.beginPath();
            tempCtx.rect(thisX, thisY, rectW, rectW);
            tempCtx.fillStyle = 'blue';
            tempCtx.fill();
            // 源图形  目标图形
            // 包括:source-atop,source-in,source-out,source-over,destination-atop,destination-in ,destination-out,destination-over,lighter,xor,copy 默认状态 :source-over
            // 设置全局组合模式
            tempCtx.globalCompositeOperation = arr[i];
            // 绘制圆
            tempCtx.beginPath();
            tempCtx.arc(thisX + circleRadius + 10, thisY + circleRadius + 10, circleRadius, 0, Math.PI * 2);
            tempCtx.fillStyle = 'red';
            tempCtx.fill();
            // 绘制文字
            tempCtx.beginPath();
            tempCtx.globalCompositeOperation = 'source-over';
            tempCtx.fillStyle = 'green';
            tempCtx.font='10px bold';
            tempCtx.fillText(arr[i], thisX, thisY + 90);

            ctx.drawImage(tempCanvas, 0, 0);
        }

    </script>

    </html>

2.2坐标转换

2.2.1原点的位移

  • 使用translate()方法可以将绘图的原点横向和纵向移动到指定的距离(x,y)。结果。一般表现是整张的一个移动。
    ctx.translate(x,y);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
        <script>
            var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');
            ctx.beginPath();
            ctx.fillStyle = "blue";
            ctx.fillRect(0, 0, 200, 200)
            ctx.translate(canvas.width / 2, canvas.height / 2);
            ctx.beginPath();
            ctx.fillStyle = "red";
            ctx.fillRect(0, 0, 200, 100)
        </script>

    </html>

2.2.2缩放

  • scale()缩放我们使用scale方法。参数分别代表横向和纵向的缩放比例,;两个参数都是浮点类型。1.0表示不缩放,小于1.0表示缩放,大于1.0表示放大。
    ctx.scale(x,y);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
        <script>
            var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');
            ctx.beginPath();
            ctx.fillStyle = "blue";
            ctx.fillRect(0, 0, 200, 200)
            ctx.scale(0.5, 0.5);
            ctx.beginPath();
            ctx.fillStyle = "red";
            ctx.fillRect(0, 0, 200, 100)
        </script>

    </html>

2.2.3旋转

  • 旋转,我们使用rotate方法。这个方法呢?接受一个以弧度为单位的旋转的参数。整个二看worth将以坐标原点,也就是由translate所确定的原点为圆心进行旋转。
    ctx.rotate(x,y);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="600" height="600"></canvas>
        <script>
            var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');
            ctx.beginPath();
            ctx.fillStyle = "blue";
            ctx.fillRect(0, 0, 200, 200)
            ctx.rotate(45 * Math.PI / 180);
            ctx.beginPath();
            ctx.fillStyle = "red";
            ctx.fillRect(0, 0, 200, 100)
        </script>

    </html>

2.2.4自定义坐标转换

  • transform()方法以用户自定义的变换矩阵对图像坐标进行变换操作。这个方法有6个参数3x3的转换矩阵 。
    ctx.transform(a,b,c,d,e,f);
参数 描述
a 水平缩放绘图
b 水平倾斜绘图
c 垂直倾斜绘图
d 垂直缩放绘图
e 水平移动绘图
f 垂直移动绘图

2.2.5镜面转换

    // 水平镜面
    ctx.scale(-1,1);
    // 垂直镜面
    ctx.scale(1,-1);
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid yellow;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>

    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');

        ctx.font = '30px bold';
        ctx.textBaseline = 'top';
        // ctx.fillText('hello world', 0, 0);
        // // 水平镜面
        // ctx.scale(-1, 1);
        // ctx.fillText('hello world', -200, 100);
        ctx.fillText('hello world', 0, 100);
        // 垂直镜面
        ctx.scale(1, -1);
        ctx.fillText('hello world', 0, -100);


    </script>

    </html>

2.2.6 变换状态栈

  • 使用save()和restore() 方法可以实现 对坐标变换状态的保存或恢复。
  • 先绘制一个倾斜45°的矩形蓝色的。在绘制一个倾斜了70°大红色矩形。再绘制一个不做倾斜,但是水平平移的,和垂直平是我的移的黄色矩形。
    ctx.save(); //保存当前坐标系状态
    ctx.restore();//恢复之前保存的坐标系状态

2.3 图像数据与 URLS

2.3.1 图像数据

  • 我们通过 getImageData()方法和图像对象的属性获取图像中每一个像素点的数据。图像数据中的每一个像素。都是由 red,green,blue 和 alpha 来表示。我们可以用 putImageData 方法来设置图像的像素值,然后呢,重画经过修改的这些图像。
    ctx.drawImage(imgObj, destX, destY);
    var imageData = ctx.getImageData(startX, startY, canvas.width, canvas.height);
    var data = imageData.data;
    // ………………………………
    // https://www.cnblogs.com/st-leslie/p/8317850.html?utm_source=debugrun&utm_medium=referral#removeColor
    // 处理像素数据
    ctx.putImageData(imageData, destX, destY);
  • 注意:getImageData()方法要求图像数据存储在 web 服务器上,并且操作这个图像的代码必须是在同一台服务器上,如果这两条中的任意一条不满足的话,将会抛出 SECURITY_ERR 异常。

2.3.2 获取图像数据 URL

  • 通过 toDataURL()方法,可以得到一个指向当前图像的 64 bit png 格式图像文件的 URL;
    canvas.toDataURL();

    // 绘制已知的图像的数据URL
    // urls == 已知的图像的数据URL  64bit 数据格式
    var img = new Image();
    img.src = urls;
    img.onload = function() {
    myctx.drawImage(this, 0, 0);
    };
  • 注意:图像在同一个主机上。

2.4 动画

2.4.1 清除canvas上的内容

    ctx.clearRect(startX||0,startY||0,canvas.width,canvas.height);

2.4.2 使用requestAnimationFrame 方法创建动画

  • 如果要用canvas创建动画,推荐大家使用requestAnimationFrame方法。这个方法能够使浏览器智能的去判断它帧率,这个帧率一般我们叫什么FPS。而且对于动画的每一帧我们都可以进行更新清除canvas,然后再申请下一帧的动画。
  • 接收一个由用户自定义的回调函数对象作为参数。方法呢,会在当前帧完成后自定自动去调用这个回调函数,而我们要做的就是在这个回调函数。中实现或者进行我们下一步下一帧图像绘制的一个操作,并且啊,在这个回调函数最后再次调用requestAnimationFrame方法使得这个动画的执行可以一帧一帧的连续绘制下去。
  • 由于不同的浏览器对该方法的支持程度不同。所以使用的时候一般要稍微做一些处理。
    window.requestAnimFrame=(function(callback){
    return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(callback){
        window.setTimeout(callback,1000/60);
    }
    })
    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            canvas {
                border: 3px solid red;
            }
        </style>
    </head>

    <body>
        <canvas id="canvas" width="1000" height="1000"></canvas>
    </body>
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        

        function Rect(obj){
            this.x=obj.x||0;
            this.y=obj.y||0;
            this.w=obj.w||50;
            this.h=obj.h||50;
            this.fcolor=obj.fcolor||'black';
            this.scolor=obj.scolor||'red';
            // this.speed=obj.speed||{sx:1,sy:1};
            this.speedx=obj.sx;
            this.speedy=obj.sy;
        }
        
        Rect.prototype.move=function(){
            this.x+=this.speedx;
            this.y+=this.speedy;
        }

        Rect.prototype.draw=function(ctx){
            ctx.beginPath();
            ctx.fillStyle=this.fcolor;
            ctx.strokeStyle=this.scolor;
            ctx.fillRect(this.x,this.y,this.w,this.h);
            ctx.strokeRect(this.x,this.y,this.w,this.h);
            
        }

        var objArr=[];
        // 创建指定的对象
        function  createRect(){
            var rect1=new Rect({x:10,y:10,w:40,h:40,fcolor:'red',sx:5,sy:2});
            objArr.push(rect1);

            var rect2=new Rect({x:800,y:600,w:140,h:140,fcolor:'red',sx:-15,sy:-20});
            objArr.push(rect2);
        }
        createRect();

    var requestAniObj=null;
    var flag=true;
        function Animate(){ 
            // 清屏
            ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
            // 绘制下一帧动画;
            for(var i=0;i<objArr.length;i++){
                objArr[i].move();
                objArr[i].draw(ctx);
            }
            // 请求下一帧动画的调用
            // requestAnimationFrame(function(){
            //     Animate();
            // })
            requestAniObj=requestAnimationFrame(Animate);
        }
        Animate();
        canvas.onclick=function(){
            flag=!flag;
            if(!flag){
                cancelAnimationFrame(requestAniObj);
            }else{
                Animate();
            }
        }


    </script>

    </html>
    <!DOCTYPE html>
    <html>
    <head>
        <style>
        body {
            margin: 0px;
            padding: 0px;
        }

        canvas {
            border: 2px solid #f00;
        }

        .btn {
            padding: 10px 25px;
            background: #f00;
            color: #fff;
            display: inline-block;
            border-radius: 4px;
        }
        </style>
    </head>

    <body>
        <canvas id="myCanvas" width="1000" height="630"></canvas>
        <div class="btn">创建矩形</div>
        <script>
        // CanvasRenderingContext2D.prototype.objArr=[];
        CanvasRenderingContext2D.prototype.draw = function(obj) {
            this.beginPath();
            this.fillStyle = obj.fc;
            this.strokeStyle = obj.sc;
            if (obj.isfill) {
            this.fillRect(obj.x, obj.y, obj.w, obj.h);
            }
            if (obj.isstroke) {
            this.strokeRect(obj.x, obj.y, obj.w, obj.h);
            }
            // for(var i in objArr){
            //   objArr[i]
            // }
        };
        CanvasRenderingContext2D.prototype.clear = function() {
            this.clearRect(0, 0, this.canvas.width, this.canvas.height);
        };

        // 全局时间轴
        var time = 0;
        var allRectArr = [];
        var myCanvas = document.getElementById("myCanvas");
        var myctx = myCanvas.getContext("2d");

        // 矩形对象 构造函数
        function Rect(obj) {
            this.x = obj.x || 0;
            this.y = obj.y || 0;
            this.w = obj.w || 50;
            this.h = obj.h || 50;
            this.vx = obj.vx || 2;
            this.vy = obj.vy || 2;
            this.isstroke = false;
            this.isfill = true;
            this.fc = obj.fc || "#999";
            this.sc = obj.sc || "black";
        }
        Rect.prototype.move = function() {
            // 线性运动
            this.x += this.vx;
        };
        // 1000/60==fps
        // 16     16.6
        // 0----1----2----3----4----5----6

        function Animate() {
            time++;
            // 清除画布
            myctx.clear();
            // 间隔一段时间 创建一个新的元素   1600 毫秒
            if (time % 100 == 0) {
            var rData = {
                x: 0,
                y: getRandom(0, 600),
                w: getRandom(50, 150),
                h: getRandom(50, 150),
                fc:
                "rgb(" +
                getRandom(0, 255) +
                "," +
                getRandom(0, 255) +
                "," +
                getRandom(0, 255) +
                ")",
                vx: getRandom(1, 10),
                vy: 0
            };
            // debugger
            var r = new Rect(rData);
            allRectArr.push(r);
            }
            // if(time%1==0){
            //   for(var i in allRectArr){
            //     allRectArr[i].move();
            //   }
            // }

            for (var i in allRectArr) {
            myctx.draw(allRectArr[i]);
            // 加速运动
            allRectArr[i].vx += 1;
            allRectArr[i].move();
            }
            // 并且移动 绘制   3200毫秒

            requestAnimationFrame(Animate);
        }
        Animate();

        function getRandom(min, max) {
            return Math.round(Math.random() * (max - min) + min);
        }

        // Math.prototype.getRandom = function (min, max) {
        //   return Math.round(Math.random() * (max - min) + min);
        // }

        // //渲染 行为
        // Rect.prototype.draw = function () {

        // }
        // // 移动 行为  线性运动
        // Rect.prototype.move = function () {

        // }

        // requestAnimationFrame()  递归函数 有时间(间隔--系统自动调整)控制的

        // 震荡效果
        // 计算像素的坐标位置:x(时间)+振幅*sin(时间*2PI/周期)+x0.(x0为像素点的原始位置)
        </script>
    </body>
    </html>
  • 震荡效果
    <!DOCTYPE html>
    <html>
    <head>
        <style>
        body {
            margin: 0px;
            padding: 0px;
        }

        canvas {
            border: 2px solid #f00;
        }

        .btn {
            padding: 10px 25px;
            background: #f00;
            color: #fff;
            display: inline-block;
            border-radius: 4px;
        }
        </style>
    </head>

    <body>
        <canvas id="myCanvas" width="1000" height="630"></canvas>
        <div class="btn">创建矩形</div>
        <script>
        var myCanvas = document.getElementById("myCanvas");
        var ctx = myCanvas.getContext("2d");

        var myRect = {
            x: 0,
            y: 0,
            w: 100,
            h: 50,
            bw: 5
        };
        // 震荡效果
        // 计算像素的坐标位置:振幅*sin(时间*2PI/周期)+x0.(x0为像素点的原始位置)

        function animate() {
            // 清除
            ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
            // 清除
            // 更新图像
            var date = new Date();
            var time = date.getTime(); //时间
            var amplitude = 250; //振幅
            var period = 600; //周期
            var centerX = myCanvas.width / 2 - myRect.w / 2;
            var nextX =
            amplitude * Math.sin((time * 2 * Math.PI) / period) + centerX;

            myRect.x = nextX;
            // 更新图像
            // 绘图
            ctx.beginPath();
            ctx.fillStyle = "orange";
            ctx.strokeStyle = "#f00";
            ctx.lineWidth = myRect.bw;
            ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
            // 绘图
            // 请求下一帧
            requestAnimationFrame(animate);
            // 请求下一帧
        }

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