整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

canvas-3圆的绘制

canvas-3圆的绘制


anvas画了一个圆


<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>canvas-3圆的绘制</title>

</head>

<body>

<canvas id="canvas1" width="600" height="600" style="border:1px solid #000000"></canvas>

<script type="text/javascript">

var canvas1=document.querySelector("#canvas1") // 1.找到画布对象

var ctx=canvas1.getContext("2d") // 2.上下文对象(画笔)

// (圆心x:300, 圆心y:300, 半径r:100, 开始角度:0 , 结束角度:360度, 默认为false(可不写)是顺时针,true为逆时针)

ctx.arc(300, 300, 100, 0, 2*Math.PI)

ctx.stroke()

</script>


</body>

</html>


家好! 欢迎来到本教程,我们将深入了解使用 HTML 画布和 JavaScript 在代码中创建有趣的气泡的世界。 最好的部分? 我们将只使用一点 HTML 和所有 JavaScript,而不是 CSS 来实现所有这一切。

揭示概念

今天,我们要掌握以下几个概念:

使用画布上下文的 arc 方法创建圆。

利用 requestAnimationFrame 函数实现平滑的圆形动画。

利用 JavaScript 类的强大功能来创建多个圆圈,而无需重复代码。

向我们的圆圈添加描边样式和填充样式以获得 3D 气泡效果。

你可以跟着我一起看,或者如果你想看源代码,可以使用最终的codepen

入门

首先,我们需要一个 HTML5 Canvas 元素。 Canvas 是创建形状、图像和图形的强大元素。 这就是气泡将产生的地方。 让我们来设置一下 -

<canvas id="canvas"></canvas>

为了使用画布做任何有意义的事情,我们需要访问它的上下文。 Context 提供了在画布上渲染对象和绘制形状的接口。

让我们访问画布及其上下文。

const canvas=document.getElementById('canvas');
const context=canvas.getContext('2d');

我们将设置画布以使用整个窗口的高度和宽度 -

canvas.width=window.innerWidth;
canvas.height=window.innerHeight;

让我们通过添加一些 css 为画布提供一个漂亮的舒缓浅蓝色背景。 这是我们要使用的唯一 CSS。 如果您愿意,也可以使用 JavaScript 来完成此操作。

#canvas {
  background: #00b4ff;
}

是时候创造泡泡了!

让我们进入有趣的部分。 我们将通过单击画布来创建气泡。 为了实现这一点,我们首先创建一个点击事件处理程序:

canvas.addEventListener('click', handleDrawCircle);

由于我们需要知道在画布上单击的位置,因此我们将在句柄 DrawCircle 函数中跟踪它并使用事件的坐标 -

//We are adding x and y here because we will need it later.
let x, y
const handleDrawCircle=(event)=> {
  x=event.pageX;
  y=event.pageY;

// Draw a bubble!
  drawCircle(x, y);
};

用圆弧法画圆

为了创建圆圈,我们将利用画布上下文中可用的 arc 方法。 Arc 方法接受 x 和 y - 圆心、半径、起始角和结束角,对于我们来说,这将是 0 和 2* Math.PI,因为我们正在创建一个完整的圆。

const drawCircle=(x, y)=> {
  context.beginPath();
  context.arc(x, y, 50, 0, 2 * Math.PI);
  context.strokeStyle='white';
  context.stroke();
};


使用 requestAnimationFrame 方法移动圆圈

现在我们有了圆圈,让我们让它们移动,因为……

GIF



请记住,当我们创建圆时,我们使用了 arc 方法,它接受 x 和 y 坐标 - 圆的中心。 如果我们快速移动圆的 x 和 y 坐标,就会给人一种圆在移动的印象。 让我们试试吧!

//Define a speed by which to increment to the x and y coordinates
const dx=Math.random() * 3;
const dy=Math.random() * 7;//Increment the center of the circle with this speed
x=x + dx;
y=y - dy;

我们可以将其移至函数内 -

let x, y;
const move=()=> {
  const dx=Math.random() * 3;
  const dy=Math.random() * 7;  x=x + dx;
  y=y - dy;
};

为了让我们的圆圈无缝移动,我们将创建一个动画函数并使用浏览器的 requestAnimationFrame 方法来创建一个移动的圆圈。

const animate=()=> {
  context.clearRect(0, 0, canvas.width, canvas.height);
  move();
    drawCircle(x,y);  requestAnimationFrame(animate);
};//Don't forget to call animate at the bottom 
animate();



创建粒子:引入粒子类

现在我们已经创建了一个圆圈,是时候创建多个圆圈了!

但在我们创建多个圆圈之前,让我们准备一下我们的代码。为了避免重复我们的代码,我们将使用类并引入 Particle 类。 粒子是我们动态艺术作品和动画的构建块。 每个气泡都是一个粒子,具有自己的位置、大小、运动和颜色属性。 让我们定义一个 Particle 类来封装这些属性:

class Particle {
  constructor(x=0, y=0) {}
  draw() {
    // Drawing the particle as a colored circle
    // ...
  }  move() {
    // Implementing particle movement
    // ...
  }
}

让我们将一些已设置的常量移至 Particle 类 -

class Particle {
  constructor(x=0, y=0) {
    this.x=x;
    this.y=y;
    this.radius=Math.random() * 50;
    this.dx=Math.random() * 3;
    this.dy=Math.random() * 7;
  }
  draw() {
    // Drawing the particle as a colored circle
    // ...
  }  move() {
    // Implementing particle movement
    // ...
  }
}

draw 方法将负责在画布上渲染粒子。 我们已经在drawCircle中实现了这个功能,所以让我们将它移动到我们的类中并将变量更新为类变量

class Particle {
  constructor(x=0, y=0) {
    this.x=x;
    this.y=y;
    this.radius=Math.random() * 50;
    this.dx=Math.random() * 3;
    this.dy=Math.random() * 7;
    this.color='white';
  }
  draw() {
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    context.strokeStyle=this.color;
    context.stroke();    context.fillStyle=this.color;
    context.fill();
  }  move() {}
}

同样,让我们在类中移动 move 函数 -

move() {
    this.x=this.x + this.dx;
    this.y=this.y - this.dy;
}

现在,我们需要确保在事件处理程序中调用 Particle 类。

const handleDrawCircle=(event)=> {
  const x=event.pageX;
  const y=event.pageY;
  const particle=new Particle(x, y);
};canvas.addEventListener('click', handleDrawCircle);

由于我们需要在 animate 函数中访问该粒子,以便调用其 move 方法,因此我们将该粒子存储在一个名为 molecularArray 的数组中。 当创建大量粒子时,这个数组也会很有帮助。 这是反映这一点的更新代码 -

const particleArray=[];
const handleDrawCircle=(event)=> {
  const x=event.pageX;
  const y=event.pageY;  const particle=new Particle(x, y);
  particleArray.push(particle);
};canvas.addEventListener('click', handleDrawCircle);

记得也要更新动画功能 -

此时,您将在屏幕上看到这个粒子 -



惊人的! 现在,到了有趣的部分! 让我们创建很多圆圈并设计它们的样式,使它们看起来像气泡。

为了创建大量气泡,我们将使用 for 循环创建粒子并将它们添加到我们在此处创建的粒子数组中。

const handleDrawCircle=(event)=> {
  const x=event.pageX;
  const y=event.pageY;
  for (let i=0; i < 50; i++) {
    const particle=new Particle(x, y);
    particleArray.push(particle);
  }
};canvas.addEventListener('click', handleDrawCircle);

在动画函数中,我们将通过清除画布并在新位置重新绘制粒子来不断更新画布。 这会给人一种圆圈在移动的错觉。

const animate=()=> {
  context.clearRect(0, 0, canvas.width, canvas.height);
  particleArray.forEach((particle)=> {
    particle?.move();
    particle?.draw();
  });  requestAnimationFrame(animate);
};animate();


现在我们有了移动的气泡,是时候给它们添加颜色,使它们看起来像气泡了!

我们将通过向气泡添加渐变填充来实现此目的。 这可以使用 context.createRadialGradient 方法来完成。

const gradient=context.createRadialGradient(
  this.x,
  this.y,
  1,
  this.x + 0.5,
  this.y + 0.5,
  this.radius
);
gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.3)');
gradient.addColorStop(0.95, '#e7feff');context.fillStyle=gradient;



总结

恭喜! 您刚刚仅使用 HTML Canvas 和 JavaScript 创建了一些超级有趣的东西。 您已经学习了如何使用 arc 方法、利用 requestAnimationFrame、利用 JavaScript 类的强大功能以及使用渐变设计气泡以实现 3D 气泡效果。

请随意尝试颜色、速度和大小,使您的动画真正独一无二。

请随意尝试颜色、速度和大小,使您的动画真正独一无二。

我希望您在学习本教程时能像我在创建它时一样获得乐趣。 现在,轮到你进行实验了。 我很想看看你是否尝试过这个以及你创造了什么。 与我分享您的代码链接,我很乐意查看。

. canvas实现时钟转动

实现以下效果,分为几步:

  • 1、找到canvas的中心,画出表心,以及表框
  • 2、获取当前时间,并根据时间画出时针,分针,秒针,还有刻度
  • 3、使用定时器,每过一秒获取新的时间,并重新绘图,达到时钟转动的效果

1.1 表心,表框

画表心,表框有两个知识点:

  • 1、找到canvas的中心位置
  • 2、绘制圆形
//html
<canvas id="canvas" width="600" height="600"></canvas>
// js
// 设置中心点,此时300,300变成了坐标的0,0
ctx.translate(300, 300)
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
// 执行画线段的操作stroke
ctx.stroke() 
复制代码

让我们来看看效果,发现了,好像不对啊,我们是想画两个独立的圆线,怎么画出来的两个圆连到一起了

原因是:上面代码画连个圆时,是连着画的,所以画完大圆后,线还没斩断,就接着画小圆,那肯定会大圆小圆连一起,解决办法就是:beginPath,closePath

ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
// 画大圆
+ ctx.beginPath()
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
  ctx.arc(0, 0, 100, 0, 2 * Math.PI)
  ctx.stroke() // 执行画线段的操作
+ ctx.closePath()
// 画小圆
+ ctx.beginPath()
  ctx.arc(0, 0, 5, 0, 2 * Math.PI)
  ctx.stroke()
+ ctx.closePath()
复制代码

1.2 时针,分针,秒针

画这三个指针,有两个知识点:

  • 1、根据当前时,分,秒计算角度
  • 2、在计算好的角度上去画出时针,分针,秒针

如何根据算好的角度去画线呢,比如算出当前是3点,那么时针就应该以12点为起始点,顺时针旋转2 * Math.PI / 12 * 3=90°,分针和秒针也是同样的道理,只不过跟时针不同的是比例问题而已,因为时在表上有12份,而分针和秒针都是60份

这时候又有一个新问题,还是以上面的例子为例,我算出了90°,那我们怎么画出时针呢?我们可以使用moveTo和lineTo去画线段。至于90°,我们只需要将x轴顺时针旋转90°,然后再画出这条线段,那就得到了指定角度的指针了。但是上面说了,是要以12点为起始点,我们的默认x轴确是水平的,所以我们时分秒针算出角度后,每次都要减去90°。可能这有点绕,我们通过下面的图演示一下,还是以上面3点的例子:

这样就得出了3点指针的画线角度了。

又又又有新问题了,比如现在我画完了时针,然后我想画分针,x轴已经在我画时针的时候偏转了,这时候肯定要让x轴恢复到原来的模样,我们才能继续画分针,否则画出来的分针是不准的。这时候save和restore就派上用场了,save是把ctx当前的状态打包压入栈中,restore是取出栈顶的状态并赋值给ctxsave可多次,但是restore取状态的次数必须等于save次数

懂得了上面所说,剩下画刻度了,起始刻度的道理跟时分秒针道理一样,只不过刻度是死的,不需要计算,只需要规则画出60个小刻度,和12个大刻度就行

const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
// 把状态保存起来
+ ctx.save()
// 画大圆
ctx.beginPath()
// 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
ctx.arc(0, 0, 100, 0, 2 * Math.PI)
ctx.stroke() // 执行画线段的操作
ctx.closePath()
// 画小圆
ctx.beginPath()
ctx.arc(0, 0, 5, 0, 2 * Math.PI)
ctx.stroke()
ctx.closePath()
----- 新加代码  ------
// 获取当前 时,分,秒
let time=new Date()
let hour=time.getHours() % 12
let min=time.getMinutes()
let sec=time.getSeconds()
// 时针
ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
ctx.beginPath()
// moveTo设置画线起点
ctx.moveTo(-10, 0)
// lineTo设置画线经过点
ctx.lineTo(40, 0)
// 设置线宽
ctx.lineWidth=10
ctx.stroke()
ctx.closePath()
// 恢复成上一次save的状态
ctx.restore()
// 恢复完再保存一次
ctx.save()
// 分针
ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(60, 0)
ctx.lineWidth=5
ctx.strokeStyle='blue'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
//秒针
ctx.rotate(2 * Math.PI / 60 * sec -  - Math.PI / 2)
ctx.beginPath()
ctx.moveTo(-10, 0)
ctx.lineTo(80, 0)
ctx.strokeStyle='red'
ctx.stroke()
ctx.closePath()
ctx.restore()
ctx.save()
// 绘制刻度,也是跟绘制时分秒针一样,只不过刻度是死的
ctx.lineWidth=1
for (let i=0; i < 60; i++) {
    ctx.rotate(2 * Math.PI / 60)
    ctx.beginPath()
    ctx.moveTo(90, 0)
    ctx.lineTo(100, 0)
    // ctx.strokeStyle='red'
    ctx.stroke()
    ctx.closePath()
}
ctx.restore()
ctx.save()
ctx.lineWidth=5
for (let i=0; i < 12; i++) {
    ctx.rotate(2 * Math.PI / 12)
    ctx.beginPath()
    ctx.moveTo(85, 0)
    ctx.lineTo(100, 0)
    ctx.stroke()
    ctx.closePath()
}
ctx.restore()
复制代码

最后一步就是更新视图,使时钟转动起来,第一想到的肯定是定时器setInterval,但是注意一个问题:每次更新视图的时候都要把上一次的画布清除,再开始画新的视图,不然就会出现千手观音的景象

附上最终代码:

const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
setInterval(()=> {
    ctx.save()
    ctx.clearRect(0, 0, 600, 600)
    ctx.translate(300, 300) // 设置中心点,此时300,300变成了坐标的0,0
    ctx.save()
    // 画大圆
    ctx.beginPath()
    // 画圆线使用arc(中心点X,中心点Y,半径,起始角度,结束角度)
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.stroke() // 执行画线段的操作
    ctx.closePath()
    // 画小圆
    ctx.beginPath()
    ctx.arc(0, 0, 5, 0, 2 * Math.PI)
    ctx.stroke()
    ctx.closePath()
    // 获取当前 时,分,秒
    let time=new Date()
    let hour=time.getHours() % 12
    let min=time.getMinutes()
    let sec=time.getSeconds()
    // 时针
    ctx.rotate(2 * Math.PI / 12 * hour + 2 * Math.PI / 12 * (min / 60) - Math.PI / 2)
    ctx.beginPath()
    // moveTo设置画线起点
    ctx.moveTo(-10, 0)
    // lineTo设置画线经过点
    ctx.lineTo(40, 0)
    // 设置线宽
    ctx.lineWidth=10
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()
    // 分针
    ctx.rotate(2 * Math.PI / 60 * min + 2 * Math.PI / 60 * (sec / 60) - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10, 0)
    ctx.lineTo(60, 0)
    ctx.lineWidth=5
    ctx.strokeStyle='blue'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()
    //秒针
    ctx.rotate(2 * Math.PI / 60 * sec - Math.PI / 2)
    ctx.beginPath()
    ctx.moveTo(-10, 0)
    ctx.lineTo(80, 0)
    ctx.strokeStyle='red'
    ctx.stroke()
    ctx.closePath()
    ctx.restore()
    ctx.save()
    // 绘制刻度,也是跟绘制时分秒针一样,只不过刻度是死的
    ctx.lineWidth=1
    for (let i=0; i < 60; i++) {
        ctx.rotate(2 * Math.PI / 60)
        ctx.beginPath()
        ctx.moveTo(90, 0)
        ctx.lineTo(100, 0)
        // ctx.strokeStyle='red'
        ctx.stroke()
        ctx.closePath()
    }
    ctx.restore()
    ctx.save()
    ctx.lineWidth=5
    for (let i=0; i < 12; i++) {
        ctx.rotate(2 * Math.PI / 12)
        ctx.beginPath()
        ctx.moveTo(85, 0)
        ctx.lineTo(100, 0)
        // ctx.strokeStyle='red'
        ctx.stroke()
        ctx.closePath()
    }
    ctx.restore()
    ctx.restore()
}, 1000)
复制代码

效果 very good啊:

2. canvas实现刮刮卡

小时候很多人都买过充值卡把,懂的都懂啊哈,用指甲刮开这层灰皮,就能看底下的答案了。

思路是这样的:

  • 1、底下答案是一个div,顶部灰皮是一个canvascanvas一开始盖住div
  • 2、鼠标事件,点击时并移动时,鼠标经过的路径都画圆形开路,并且设置globalCompositeOperationdestination-out,使鼠标经过的路径都变成透明,一透明,自然就显示出下方的答案信息。

关于fill这个方法,其实是对标stroke的,fill是把图形填充,stroke只是画出边框线

// html
<canvas id="canvas" width="400" height="100"></canvas>
<div class="text">恭喜您获得100w</div>
<style>
        * {
            margin: 0;
            padding: 0;
        }
        .text {
            position: absolute;
            left: 130px;
            top: 35px;
            z-index: -1;
        }
</style>
// js
const canvas=document.getElementById('canvas')
const ctx=canvas.getContext('2d')
// 填充的颜色
ctx.fillStyle='darkgray'
// 填充矩形 fillRect(起始X,起始Y,终点X,终点Y)
ctx.fillRect(0, 0, 400, 100)
ctx.fillStyle='#fff'
// 绘制填充文字
ctx.fillText('刮刮卡', 180, 50)
let isDraw=false
canvas.onmousedown=function () {
    isDraw=true
}
canvas.onmousemove=function (e) {
    if (!isDraw) return
    // 计算鼠标在canvas里的位置
    const x=e.pageX - canvas.offsetLeft
    const y=e.pageY - canvas.offsetTop
    // 设置globalCompositeOperation
    ctx.globalCompositeOperation='destination-out'
    // 画圆
    ctx.arc(x, y, 10, 0, 2 * Math.PI)
    // 填充圆形
    ctx.fill()
}
canvas.onmouseup=function () {
    isDraw=false
}
复制代码

效果如下:

3. canvas实现画板和保存

框架:使用vue + elementUI

其实很简单,难点有以下几点:

  • 1、鼠标拖拽画正方形和圆形
  • 2、画完一个保存画布,下次再画的时候叠加
  • 3、保存图片

第一点,只需要计算出鼠标点击的点坐标,以及鼠标的当前坐标,就可以计算了,矩形长宽计算:x - beginX, y - beginY,圆形则要利用勾股定理:Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))

第二点,则要利用canvas的getImageDataputImageData方法

第三点,思路是将canvas生成图片链接,并赋值给具有下载功能的a标签,并主动点击a标签进行图片下载

看看效果吧:

具体代码我就不过多讲解了,说难也不难,只要前面两个项目理解了,这个项目很容易就懂了:

<template>
  <div>
    <div style="margin-bottom: 10px; display: flex; align-items: center">
      <el-button @click="changeType('huabi')" type="primary">画笔</el-button>
      <el-button @click="changeType('rect')" type="success">正方形</el-button>
      <el-button
        @click="changeType('arc')"
        type="warning"
        style="margin-right: 10px"
        >圆形</el-button
      >
      <div>颜色:</div>
      <el-color-picker v-model="color"></el-color-picker>
      <el-button @click="clear">清空</el-button>
      <el-button @click="saveImg">保存</el-button>
    </div>
    <canvas
      id="canvas"
      width="800"
      height="400"
      @mousedown="canvasDown"
      @mousemove="canvasMove"
      @mouseout="canvasUp"
      @mouseup="canvasUp"
    >
    </canvas>
  </div>
</template>
<script>
export default {
  data() {
    return {
      type: "huabi",
      isDraw: false,
      canvasDom: null,
      ctx: null,
      beginX: 0,
      beginY: 0,
      color: "#000",
      imageData: null,
    };
  },
  mounted() {
    this.canvasDom=document.getElementById("canvas");
    this.ctx=this.canvasDom.getContext("2d");
  },
  methods: {
    changeType(type) {
      this.type=type;
    },
    canvasDown(e) {
      this.isDraw=true;
      const canvas=this.canvasDom;
      this.beginX=e.pageX - canvas.offsetLeft;
      this.beginY=e.pageY - canvas.offsetTop;
    },
    canvasMove(e) {
      if (!this.isDraw) return;
      const canvas=this.canvasDom;
      const ctx=this.ctx;
      const x=e.pageX - canvas.offsetLeft;
      const y=e.pageY - canvas.offsetTop;
      this[`${this.type}Fn`](ctx, x, y);
    },
    canvasUp() {
      this.imageData=this.ctx.getImageData(0, 0, 800, 400);
      this.isDraw=false;
    },
    huabiFn(ctx, x, y) {
      ctx.beginPath();
      ctx.arc(x, y, 5, 0, 2 * Math.PI);
      ctx.fillStyle=this.color;
      ctx.fill();
      ctx.closePath();
    },
    rectFn(ctx, x, y) {
      const beginX=this.beginX;
      const beginY=this.beginY;
      ctx.clearRect(0, 0, 800, 400);
      this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
      ctx.beginPath();
      ctx.strokeStyle=this.color;
      ctx.rect(beginX, beginY, x - beginX, y - beginY);
      ctx.stroke();
      ctx.closePath();
    },
    arcFn(ctx, x, y) {
      const beginX=this.beginX;
      const beginY=this.beginY;
      this.isDraw && ctx.clearRect(0, 0, 800, 400);
      this.imageData && ctx.putImageData(this.imageData, 0, 0, 0, 0, 800, 400);
      ctx.beginPath();
      ctx.strokeStyle=this.color;
      ctx.arc(
        beginX,
        beginY,
        Math.round(
          Math.sqrt((x - beginX) * (x - beginX) + (y - beginY) * (y - beginY))
        ),
        0,
        2 * Math.PI
      );
      ctx.stroke();
      ctx.closePath();
    },
    saveImg() {
      const url=this.canvasDom.toDataURL();
      const a=document.createElement("a");
      a.download="sunshine";
      a.href=url;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    },
    clear() {
        this.imageData=null
        this.ctx.clearRect(0, 0, 800, 400)
    }
  },
};
</script>
<style lang="scss" scoped>
#canvas {
  border: 1px solid black;
}
</style>
复制代码

结语

公众号:小何成长,佛系更文,都是自己曾经踩过的坑或者是学到的东西

有兴趣的小伙伴欢迎关注我哦,我是:何小玍。 大家一起进步鸭