JavaScript进阶--一文带你认识动画与Canvas 图形

一、前言

动画这方面不太熟悉,自己用的也很少,只能在用到和看到相关书籍学习的时候将经验写下来了...

二、requestAnimationFrame

1.早期定时动画

说明: 早期动画使用setInterval来控制动画的运行,不过存在两个问题,一是定时的间隔必须足够短,这样才能让不同的动画类型运行流畅,但是又需要足够长,以便浏览器可以将变化渲染出来,大部分屏幕的刷新率都是60hz,意味着每秒重绘60次,那么最佳的时间间隔就是17ms了,二是定时器的第二个参数表示隔多久将代码添加到任务队列中去,并不能保证动画立即执行,因此也就不能保证动画时间的精度

js 复制代码
(function() { 
  function updateAnimations() { 
    doAnimation1(); 
    doAnimation2(); 
    // 其他任务
  } 
  setInterval(updateAnimations, 100); 
})(); 

浏览器计时器的精度:

  • IE8及更早:15.625毫秒
  • IE9及更晚:4毫秒
  • Firefox 和 Safari:约为10毫秒
  • Chrome:4毫秒

2.requestAnimationFrame

说明: 参数是一个回调函数,用于告诉浏览器在下次重绘之前调用指定的回调函数更新动画,不过它只会调用一次传入的函数,如果需要动画循环下去,可以把多个 requestAnimationFrame()调用串联起来

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Progress Bar Animation</title>
    <style>
      #status {
        width: 10%;
        height: 20px;
        background-color: #4caf50;
      }
    </style>
  </head>
  <body>
    <div id="status"></div>

    <script>
      function updateProgress(e) {
        // 回调函数接受一个参数,这个参数表示执行回调函数的时刻
        console.log(e);

        var div = document.getElementById("status");
        div.style.width = div.offsetWidth + 5 + "px";
        if (div.offsetWidth < window.innerWidth) {
          requestAnimationFrame(updateProgress);
        }
      }

      requestAnimationFrame(updateProgress);
    </script>
  </body>
</html>

3.cancelAnimationFrame

说明: 上面那个方法的返回值是一个整数ID,可以通过这个ID像定时器那样取消已经注册的动画,取消时使用的是最后一个ID

js 复制代码
let requestID = window.requestAnimationFrame(() => { 
  // 不会执行
  console.log('Repaint!'); 
}); 
window.cancelAnimationFrame(requestID);

三、canvas

说明: 创建<canvas>元素时至少要设置其widthheight属性,这样才能告诉浏览器在多大面积上绘图。出现在开始和结束标签之间的内容是后备数据,会在浏览器不支持<canvas>元素时显示,对于样式来说,可以像其它元素那样,通过css添加,也可以通过JavaScript来添加,在画布上面绘制图形,需要获取绘图上下文,可以通过getContext()来获取,对于平面图形,参数传"2d",在图形绘制完毕后,可以通过toDataURL()方法将画布上面的图形导出来

html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  // 检测元素是否存在getContext,确保浏览器支持<canvas>
  if (drawing.getContext) {
    // 取得图像的数据 URI
    let imgURI = drawing.toDataURL("image/png");
    // 显示图片
    let image = document.createElement("img");
    image.src = imgURI;
    document.body.appendChild(image);
  }
</script>

四、context

说明: context用来表示绘图上下文,这里说的是2d绘图上下文,它提供了绘制 2D 图形的方法,包括矩形、弧形和路径。2D 上下文的坐标原点(0, 0)在 元素的左上角。所有坐标值都相对于该点计算,因此 x 坐标向右增长,y 坐标向下增长。默认情况下,width 和 height 表示两个方向上像素的最大值。

1.填充和描边

说明: 填充(fillStyle)以指定样式自动填充形状,而描边(strokeStyle)只为图形边界着色,这两个属性可以是字符串、渐变对象或图案对象,默认值为"#000000"。字符串表示颜色值,可以是CSS支持的任意格式:名称、十六进制代码、rgb、rgba、hsl 或 hsla

相关的方法:

  • fill():会根据当前的样式进行填充
  • stroke():会根据当前的线条进行描边
js 复制代码
let drawing = document.getElementById("drawing");
if (drawing.getContext) {
    let context = drawing.getContext("2d");
    // 设置描边
    context.strokeStyle = "red";
    // 设置填充
    context.fillStyle = "#0000ff";
}

2.绘制矩形

说明: 矩形是可以在直接在绘图上下文中绘制的图形,与其相关的方法有三个:fillRect()strokeRect()clearRect(),它们的参数都是:左上顶点的x坐标左上顶点的y坐标矩形的宽度矩形的高度,单位是像素,需要注意,如果需要填充颜色需要先填充再绘制,否则填充无法生效

  • fillRect():以指定颜色在画布上绘制并填充矩形

  • strokeRect():以指定颜色在画布上绘制矩形的轮廓

  • clearRect():在画布上擦出一个矩形,先绘制后擦除
    常用属性:

  • lineWidth:整数,表示描边的宽度

  • lineCap:线条端点的形状,取值如下

    • "butt:平头
    • "round":圆头
    • "square":方头
  • lineJoin:线条交点的形状,取值如下

    • "round:圆的
    • "bevel":平的
    • "miter":尖的
html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");

    // 绘制红色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);
    // 绘制半透明蓝色矩形
    context.fillStyle = "rgba(0,0,255,0.5)";
    context.fillRect(30, 30, 50, 50);
  }
</script>
html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");

    // 绘制红色轮廓的矩形
    context.strokeStyle = "#ff0000";
    context.strokeRect(10, 10, 50, 50);
    // 绘制半透明蓝色轮廓的矩形
    context.strokeStyle = "rgba(0,0,255,0.5)";
    context.strokeRect(30, 30, 50, 50);
  }
</script>
html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");

    // 绘制红色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);
    // 绘制半透明蓝色矩形
    context.fillStyle = "rgba(0,0,255,0.5)";
    context.fillRect(30, 30, 50, 50);
    // 在前两个矩形重叠的区域擦除一个矩形区域
    context.clearRect(40, 40, 10, 10);
  }
</script>

3.绘制路径

说明: 通过路径可以创建复杂的形状和线条。要绘制路径,必须首先调用 beginPath()方法以表示要开始绘制新路径,然后可以使用下面这些方法来进行绘制,绘制完成,使用closePath()方法结束绘制,对于判断一个点是否被一段路径所使用,可以使用isPointInPath(x,y)方法来判定,它的返回值是布尔值

  • arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆 心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是 弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为 顺时针)。
  • arcTo(x1, y1, x2, y2, radius):以给定半径 radius,经由(x1, y1)绘制一条从上一点 到(x2, y2)的弧线。
  • lineTo(x, y):绘制一条从上一点到(x, y)的直线。
  • moveTo(x, y):不绘制线条,只把绘制光标移动到(x, y),表示当前绘制的路径是一条新路径,跟之前的路径没有关系
  • quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y) 的弧线(二次贝塞尔曲线)。
  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点, 绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。
  • rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法 与 strokeRect()和 fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。
html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");

    // 获取当前时间
    let now = new Date();
    let hour = now.getHours();
    let minute = now.getMinutes();
    let second = now.getSeconds();

    // 清除画布
    context.clearRect(0, 0, drawing.width, drawing.height);

    // 创建路径
    context.beginPath();
    // 绘制外圆
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);
    // 绘制内圆,将画笔起始点移动到内圆上,表示这是一条新的路径,与不是与外圆连接的一部分
    context.moveTo(194, 100);
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);
    // 绘制分针
    context.moveTo(100, 100);
    context.lineTo(
      100 +
        60 * Math.cos((Math.PI / 30) * (minute + second / 60) - Math.PI / 2),
      100 + 60 * Math.sin((Math.PI / 30) * (minute + second / 60) - Math.PI / 2)
    );
    // 绘制时针
    context.moveTo(100, 100);
    context.lineTo(
      100 +
        50 *
          Math.cos(
            (Math.PI / 6) * ((hour % 12) + minute / 60 + second / 3600) -
              Math.PI / 2
          ),
      100 +
        50 *
          Math.sin(
            (Math.PI / 6) * ((hour % 12) + minute / 60 + second / 3600) -
              Math.PI / 2
          )
    );
    // 描画路径
    context.stroke();
  }
</script>

4.绘制文本

说明: 绘图上下文提供了两个方法:即fillText()strokeText()。这两个方法都接收4个参数:要绘制的字符串x坐标y坐标最大像素宽度(可选),前者绘制的文本是填充的,后者是描边的,绘制的结果取决于下面三个属性的设定,先设定后绘制,其次对于文本大小的确定,可以使用measureText("需要测量的文本")方法,如果使用了第四个可选参数限制文本的宽度,绘制的字符串超出了最大宽度限制,则文本会以正确的字符高度绘制,这时字符会被水平压缩,以达到限定宽度

  • font:以 CSS 语法指定的字体样式
  • textAlign:文本的对齐方式的属性
  • textBaseLine:指定文本的基线
js 复制代码
// 在前面的时钟绘制前加上下面这些就可以绘制时刻文本了

// 正常
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);

// 与开头对齐
context.textAlign = "start";
context.fillText("12", 100, 40);

// 与末尾对齐
context.textAlign = "end";
context.fillText("12", 100, 60);

5.变换

说明: 2D绘图上下文支持所有常见的绘制变换。在创建绘制上下文时,会以默认值初始化变换矩阵,从而让绘制操作如实应用到绘制结果上,可以使用下面这些方法:

  • rotate(angle):围绕原点把图像旋转 angle 弧度(约为57.3度),先旋转再绘制,不然会失效
  • scale(scaleX, scaleY):通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像。scaleX 和 scaleY 的默认值都是 1.0。
  • translate(x, y):把原点移动到(x, y)。执行这个操作后,坐标(0, 0)就会变成(x, y)。
  • transform(m1_1, m1_2, m2_1, m2_2, dx, dy):像下面这样通过矩阵乘法直接修改矩阵。
    m1_1 m1_2 dx
    m2_1 m2_2 dy
    0 0 1
  • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()。
  • save():将当前绘图上下文的设置(如颜色、线条样式等)和变换(如平移、旋转等)保存在一个栈中。这些设置和变换的保存并不包括已经绘制的图形内容,只是记录了当前的状态,状态可以保存多次的时,恢复状态会就近恢复
  • restore():恢复上一次保存的状态
html 复制代码
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");
    // 创建路径
    context.beginPath();
    // 绘制外圆
    context.arc(100, 100, 99, 0, 2 * Math.PI, false);
    // 绘制内圆
    context.moveTo(194, 100);
    context.arc(100, 100, 94, 0, 2 * Math.PI, false);
    // 移动原点到表盘中心,此时(100,100)这个点就是原点(0,0)了
    context.translate(100, 100);
    // 旋转表针,上面已经改变了原点的坐标,所以此时旋转是围绕钟的中心点旋转的
    context.rotate(1);
    // 绘制分针
    context.moveTo(0, 0);
    context.lineTo(0, -85);
    // 绘制时针
    context.moveTo(0, 0);
    context.lineTo(-65, 0);
    // 描画路径
    context.stroke();
  }
</script>
html 复制代码
<canvas id="drawing" width="1000" height="1000">A drawing of something.</canvas>

<script>
  let drawing = document.getElementById("drawing");
  if (drawing.getContext) {
    let context = drawing.getContext("2d");
    // 设置绘图上下文的填充颜色为红色。
    context.fillStyle = "#ff0000";
    // 将当前绘图状态保存到绘图状态栈中。
    context.save();
    // 设置绘图上下文的填充颜色为绿色。
    context.fillStyle = "#00ff00";
    // 平移绘图上下文的原点位置到 (100, 100)。
    context.translate(100, 100);
    // 再次将当前绘图状态保存到绘图状态栈中。
    context.save();
    // 设置绘图上下文的填充颜色为蓝色。
    context.fillStyle = "#0000ff";
    // 在当前绘图上下文的原点位置 (100, 100) 绘制宽度为 100、高度为 200 的蓝色矩形。
    context.fillRect(0, 0, 100, 200);
    // 从绘图状态栈中取出上一个保存的绘图状态,恢复为之前的状态。此时填充颜色为绿色。
    context.restore();
    // 在当前绘图上下文的原点位置 (100, 100) 绘制宽度为 100、高度为 200 的绿色矩形。
    context.fillRect(10, 10, 100, 200);
    // 再次从绘图状态栈中取出上一个保存的绘图状态,恢复为最初的状态。此时填充颜色为红色。
    context.restore();
    // 在当前绘图上下文的原点位置 (0, 0) 绘制宽度为 100、高度为 200 的红色矩形。
    context.fillRect(0, 0, 100, 200);
  }
</script>

save()保存状态之后,在没有恢复状态之前,都可以基于这个状态上进行操作,恢复之后,后续操作只能基于这个保存的状态进行操作了,如果保存的状态存在多个,恢复状态只能就近恢复,只能恢复上一次的状态

6.绘制图像

说明: 如果想把现有图像绘制到画布上,可以使用drawImage()方法,它存在如下的传参方式:

  • drawImage(<img>元素, 绘制的x, 绘制的y):绘制出来的图形与原来一样大
  • drawImage(<img>元素, 绘制的x, 绘制的y, 目标宽度,目标高度):绘制出来的图形的大小会缩放/扩大至指定的高度和宽度
  • drawImage(<img>元素, 截取的x, 截取的y, 截取宽度,截取高度,绘制的x,绘制的y,绘制宽度,绘制高度):会从图像的(x,y)处截取指定宽度和高度,并将其渲染在画布(x,y)处,大小为指定的宽高
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");
      const image = document.querySelector("img");

      if (drawing.getContext) {
        // 图片加载过程中的存在异步获取
        image.onload = function () {
          const context = drawing.getContext("2d");
          context.drawImage(image, 10, 10);
        };
      }
    </script>
  </body>
</html>

7.阴影

说明: 可以使用以下属性为已有的路径和形状生成阴影,这些属性都可以通过 context对象读写。只要在绘制图形或路径前给这些属性设置好适当的值,阴影就会自动生成

  • shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。
  • shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。
  • shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。
  • shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");

      if (drawing.getContext) {
        const context = drawing.getContext("2d");
        // 设置阴影
        context.shadowOffsetX = 5;
        context.shadowOffsetY = 5;
        context.shadowBlur = 4;
        context.shadowColor = "rgba(0, 0, 0, 0.5)";
        // 绘制红色矩形
        context.fillStyle = "#ff0000";
        context.fillRect(10, 10, 50, 50);
        // 绘制蓝色矩形
        context.fillStyle = "rgba(0,0,255,1)";
        context.fillRect(30, 30, 50, 50);
      }
    </script>
  </body>
</html>

8.渐变

说明: 创建一个线性渐变需要使用createLinearGradient(起点x,起点y,终点x,终点y)方法,如果需要径向渐变需要使用createRadialGradient(起点圆心x,起点圆心y,起点圆半径,终点圆心x,终点圆心y,终点圆半径)方法,它们都会返回一个CanvasGradient对象实例,实例上可以用下面的方法,

  • addColorStop(色标位置,CSS颜色字符串):指定渐变的颜色,色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后 一种颜色
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");

      if (drawing.getContext) {
        const context = drawing.getContext("2d");

        // 渐变对象
        let gradient = context.createLinearGradient(30, 30, 70, 70);
        // 渐变起始色
        gradient.addColorStop(0, "white");
        // 渐变终止色
        gradient.addColorStop(1, "black");
        // 绘制红色矩形
        context.fillStyle = "#ff0000";
        context.fillRect(10, 10, 50, 50);
        // 渐变色对象可以给fillStyle或strokeStyle属性赋值
        context.fillStyle = gradient;
        context.fillRect(30, 30, 50, 50);
      }
    </script>
  </body>
</html>

如果绘制的矩形在有部分不在渐变的范围内,则只有在渐变范围内的部分存在渐变

js 复制代码
// 如果上面渐变的矩形这样写,渐变就只有一点点了
context.fillStyle = gradient;
context.fillRect(50, 50, 50, 50);
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");

      if (drawing.getContext) {
        const context = drawing.getContext("2d");

        let gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
        gradient.addColorStop(0, "white");
        gradient.addColorStop(1, "black");
        // 绘制红色矩形
        context.fillStyle = "#ff0000";
        context.fillRect(10, 10, 50, 50);
        // 绘制渐变矩形
        context.fillStyle = gradient;
        context.fillRect(30, 30, 50, 50);
      }
    </script>
  </body>
</html>

9.图案

说明: 这个可以理解为背景图,用于填充和描画图形的重复图像,创建一个图案使用createPattern(<img>元素,如何重复)完成,第二个参数的值可选择的值为repeat、repeat-x、repeat-y、no-repeat,一样是先创建后填充

js 复制代码
// 举个栗子

let image = document.images[0], 
pattern = context.createPattern(image, "repeat"); 
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);

10.图像数据

说明: 获取图像数据可以使用getImageData(第一个像素的x,第一个像素的y,取取得的宽度,取得的高度)方法,返回值是一个ImageData实例,里面data属性包含所有区域内像素的信息,每个像素都包含四个值,分别对应r、g、b、a

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");

      if (drawing.getContext) {
        const context = drawing.getContext("2d");

        let gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
        gradient.addColorStop(0, "white");
        gradient.addColorStop(1, "black");
        // 绘制红色矩形
        context.fillStyle = "#ff0000";
        context.fillRect(10, 10, 50, 50);
        // 绘制渐变矩形
        context.fillStyle = gradient;
        context.fillRect(30, 30, 50, 50);

        // 获取图像坐标为(10,10)这个点的像素信息
        let imageData = context.getImageData(10, 10, 1, 1);
        console.log(imageData);
        console.log(imageData.data);
      }
    </script>
  </body>
</html>

11.合成

说明: 关于合成有两个属性,分别是globalAlphaglobalComposition Operation,前者是设置绘制的透明度,后者类似PS中的布尔运算,其取值超级多,详细可以看MDN具体的属性值

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Image Example</title>
  </head>
  <body>
    <img src="./1.jpg" width="400" alt="" style="display: none" />
    <canvas id="drawing" width="400" height="400"
      >A drawing of something.</canvas
    >

    <script>
      const drawing = document.getElementById("drawing");

      if (drawing.getContext) {
        const context = drawing.getContext("2d");

        // 绘制红色矩形
        context.fillStyle = "#ff0000";
        context.fillRect(10, 10, 50, 50);
        // 修改全局透明度
        context.globalAlpha = 0.5;
        // 绘制蓝色矩形
        context.fillStyle = "rgba(0,0,255,1)";
        context.fillRect(30, 30, 50, 50);
        // 重置
        context.globalAlpha = 0;
      }
    </script>
  </body>
</html>
相关推荐
熊的猫26 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人2 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人2 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR2 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香2 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596932 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书