皮皮盐

皮皮盐 查看完整档案

南京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

喜欢唱跳Rap篮球

个人动态

皮皮盐 回答了问题 · 2020-11-26

vue项目npm run build:打包部署上线,生成的dist 文件夹。 index页面无法正常显示

你试试npm全局装个http-server打开,或者丢tomcat下

关注 4 回答 5

皮皮盐 发布了文章 · 2020-11-03

初学canvas动画,绘制一个动态小球

1、新建画布

canvas首先是空白的,我们首先设置canvas的宽高,并找到渲染上下文

<body>
    <!-- 新建canvas元素 -->
    <canvas id="canvas"></canvas>
  </body>
window.onload = () => {
   // 获取canvasDom
  const canvas = document.getElementById("canvas");
  // 手动设置宽高
  canvas.width = 800;
  canvas.height = 800;
  // 获取上下文
  const ctx = canvas.getContext("2d");
};

2、绘制边框

此时页面已经可以看到画布了,我们可以更明显的一些绘制一个边框

window.onload = () => {
   .....
  // 获取canvas上下文
  const ctx = canvas.getContext("2d");
  // 绘制边框
  renderBorder(ctx);
};
function renderBorder(ctx) {
  // 首先获取画布的宽高
  const width = ctx.canvas.width;
  const height = ctx.canvas.height;
  ctx.beginPath(); // 开始一个新的路径
  ctx.moveTo(0, 0); // 将路径的起始点移动到左上角
  ctx.lineTo(width, 0); // 使用直线连接到右上角(并不绘制)
  ctx.lineTo(width, height); // ...右下角
  ctx.lineTo(0, height); // ...左下角
  ctx.closePath(); // 结束一个新的路径
  ctx.stroke(); // 绘制当前已知路径
}

3、绘制球

此时我们已经可以看到边框了,首先需要新建一个“球”对象,球对象有如下一些属性,再实现如何绘制这个“球”对象,这里涉及到canvas绘制圆弧路径的方法,比较特殊的这里是弧度

void ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

x:圆弧中心点x轴坐标
y: 圆弧中心点y轴坐标
radius: 半径
startAngle:圆弧起始点
endAngle: 圆弧结束点
anticlockwis(可选):Boolean类型,如果为true,则逆时针方向绘制,反之,顺时针方向绘制

window.onload = () => {
    .....
  // 绘制球
  let ball = {
    x: 0, // 当前x轴坐标
    y: 0, // 当前y轴坐标
    radius: 10, // 半径
    g: 0.1, // 重力加速度
    vx: 8, // x轴移动速度
    vy: 4, // y轴移动速度
    color: "blue", // 颜色
  };
  renderBall(ctx, ball);
};
// 绘制一个球
function renderBall(ctx, ball) {
  const x = ball.x + ball.radius; // 圆弧中心(圆心)的 x 轴坐标。
  const y = ball.y + ball.radius; // 圆弧中心(圆心)的 y 轴坐标。
  const radius = ball.radius; // 半径
  const startAngle = 0; // 圆弧的起始点
  const endAngle = 2 * Math.PI; // 圆弧的结束点
  ctx.beginPath(); // 开始一个新的路径
  ctx.arc(x, y, radius, startAngle, endAngle);
  ctx.closePath(); // 结束一个新的路径
  ctx.fillStyle = ball.color; // 颜色
  ctx.fill(); // 填充
}

4、让球动起来

这是我们已经绘制了一个球,接下来根据球在x轴与y轴上的速度移动起来,并给y轴加上重力加速度,这里需要注意的是每次绘制都需要重新清除一下画布,canvas动画本质就是每次都清除一遍画布重新绘画

window.onload = () => {
    // .....
     
    let ball = {
            x: 0, // 当前x轴坐标
            y: 0, // 当前y轴坐标
            radius: 10, // 半径
            g: 0.1, // 重力加速度
            vx: 8, // x轴移动速度
            vy: 4, // y轴移动速度
            color: "blue", // 颜色
          };

      setInterval(() => {
        // 先将之前绘制的擦除
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        // 绘制边框
        renderBorder(ctx);
        // 绘制球
        renderBall(ctx, ball);
        ball = updateBall(ball);
      }, 20);
  }


// 更新ball
function updateBall(ball) {
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy += ball.g;
  return ball;
}

5、优化

现在小球已经可以动起来了,再优化一下updateBall函数,真实一点,例如当小球调到边框的时候可以反弹回来,且每次反弹都会让加速度变慢


function updateBall(ctx, ball) {
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ball.x += ball.vx;
      ball.y += ball.vy;
      ball.vy += ball.g;
      if (ball.y + ball.radius >= height) {
        ball.y = height - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.y + ball.radius <= 0) {
        ball.y = 0 - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.x + ball.radius >= width) {
        ball.x = width - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      if (ball.x + ball.radius <= 0) {
        ball.x = 0 - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      return ball;
    }

整体代码


<!DOCTYPE html>
<html lang="zh-cn">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas小球</title>
  </head>
  <body>
    <!-- 新建canvas元素 -->
    <canvas id="canvas"></canvas>
  </body>
  <script>
    window.onload = () => {
      // 获取canvasDom
      const canvas = document.getElementById("canvas");
      // 手动设置宽高
      canvas.width = 800;
      canvas.height = 800;
      // 获取canvas上下文
      const ctx = canvas.getContext("2d");
      let ball = {
        x: 0, // 当前x轴坐标
        y: 0, // 当前y轴坐标
        radius: 10, // 半径
        g: 0.1, // 重力加速度
        vx: 8, // x轴移动速度
        vy: 4, // y轴移动速度
        color: "blue", // 颜色
      };
      setInterval(() => {
        // 先将之前绘制的擦除
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        // 绘制边框
        renderBorder(ctx);
        // 绘制球
        renderBall(ctx, ball);
        ball = updateBall(ctx, ball);
      }, 20);
    };
    // 绘制边框
    function renderBorder(ctx) {
      // 首先获取画布的宽高
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ctx.beginPath(); // 开始一个新的路径
      ctx.moveTo(0, 0); // 将路径的起始点移动到左上角
      ctx.lineTo(width, 0); // 使用直线连接到右上角(并不绘制)
      ctx.lineTo(width, height); // ...右下角
      ctx.lineTo(0, height); // ...左下角
      ctx.closePath(); // 结束一个新的路径
      ctx.stroke(); // 绘制当前已知路径
    }
    // 绘制一个球
    function renderBall(ctx, ball) {
      const x = ball.x + ball.radius; // 圆弧中心(圆心)的 x 轴坐标。
      const y = ball.y + ball.radius; // 圆弧中心(圆心)的 y 轴坐标。
      const radius = ball.radius; // 半径
      const startAngle = 0; // 圆弧的起始点
      const endAngle = 2 * Math.PI; // 圆弧的结束点
      ctx.beginPath(); // 开始一个新的路径
      ctx.arc(x, y, radius, startAngle, endAngle);
      ctx.closePath(); // 结束一个新的路径
      ctx.fillStyle = ball.color; // 颜色
      ctx.fill(); // 填充
    }
    // 更新ball
    function updateBall(ctx, ball) {
      const width = ctx.canvas.width;
      const height = ctx.canvas.height;
      ball.x += ball.vx;
      ball.y += ball.vy;
      ball.vy += ball.g;
      if (ball.y + ball.radius >= height) {
        ball.y = height - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.y + ball.radius <= 0) {
        ball.y = 0 - ball.radius;
        ball.vy = -ball.vy * 0.5;
      }
      if (ball.x + ball.radius >= width) {
        ball.x = width - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      if (ball.x + ball.radius <= 0) {
        ball.x = 0 - ball.radius;
        ball.vx = -ball.vx * 0.5;
      }
      return ball;
    }
  </script>
</html>


参考文档

[百度百科:弧度](
https://baike.baidu.com/item/...

[MDN:canvas](
https://developer.mozilla.org...

[慕课网: liuyubobobo老师的canvas教程](
https://www.imooc.com/learn/133)

查看原文

赞 0 收藏 0 评论 0

皮皮盐 回答了问题 · 2020-10-30

请问用vuecli4构建的项目是否需要安装css-loader呢?为什么无法导入外部的css文件呢?不生效

css-loader用来供webpack解析.css结尾的文件,vue-cli已经集成了
显示不出来可能是路径错误或者css权重优先级不够

关注 2 回答 2

皮皮盐 关注了用户 · 2020-10-30

天下熙熙 @ruguoai_qingshenai

青年,青年!无论受怎样的挫折和打击,都要咬着牙关挺住,因为你们完全有机会重建生活;只要不灰心丧气,每一次挫折就只不过是通往新境界的一块普通绊脚石,而绝不会置人于死命。——路遥

==============================================
{女,前端,四川成都,兴趣爱好:研究基金,感兴趣的朋友可以加我vx:2090750772请注明来源}

关注 23

皮皮盐 回答了问题 · 2020-10-23

vuecli4需要手动安装webpack吗?

不需要,vuecli内部集成了

关注 2 回答 1

皮皮盐 发布了文章 · 2020-10-21

前端基于ElementUI上传视频文件时校验分辨率和时长

最近产品提了一个需求,前端上传视频或者图片的时候需要校验分辨率,且视频要校验视频的时长,这时候心里就开始嘀咕了,JS能做到对视频的校验嘛?后来发现可以通过将file对象通过URL.createObjectURL这个方法,转换成video实体DOM,再根据loadedmetadata这个事件获取到视频的一些相关信息,因为事件本身是异步的,所以封装成了一个Promise。

// 获取视频信息
getVideoPlayerInfo(file) {
  return new Promise(resolve => {
    let videoElement = document.createElement('video');
    videoElement.src = URL.createObjectURL(file);
    videoElement.addEventListener('loadedmetadata', function() {
      resolve({
        duration: videoElement.duration,
        width: videoElement.videoWidth,
        height: videoElement.videoHeight
      });
    });
  });
},
// 校验这些信息
validateVideoPlayerInfo(file) {
    this.getVideoPlayerInfo(file).then(videoInfo => {
      const { duration, width, height } = videoInfo;
      // 这里可以对这些信息进行一些校验操作
      // ......
  });
},

图片也是类似的操作,不过可以通过new Image直接创建Image的DOM,在通过load事件获取

getImageInfo(file) {
  return new Promise(resolve => {
    const image = new Image();
    image.src = URL.createObjectURL(file);
    image.addEventListener('load', () => {
      const { width, height } = image;
      resolve({ width, height });
    });
  });
},
// 校验图片分辨率
validateImageResolutionInfo(file) {
  return new Promise((resolve, reject) => {
    this.getImageInfo(file).then(imageInfo => {
      const { width, height } = imageInfo;
      });
});

同样的操作也可以用在音频上audio

查看原文

赞 2 收藏 2 评论 0

皮皮盐 赞了文章 · 2020-09-28

浅谈js防抖和节流

防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。(信我,你看完肯定就懂了)

从滚动条监听的例子说起

先说一个常见的功能,很多网站会提供这么一个按钮:用于返回顶部。
返回顶部按钮

这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求-- 监听浏览器滚动事件,返回当前滚条与顶部的距离
这个需求很简单,直接写:

function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll  = showTop

但是!

图片描述

在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次
图片描述

然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

防抖(debounce)

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

当然 上述代码是为了贴合思路,方便理解(这么贴心不给个赞咩?),写完会发现其实 time = setTimeout(fn,delay)是一定会执行的,所以可以稍微简化下:


/*****************************简化后的分割线 ******************************/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置

此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。

到这里,已经把防抖实现了,现在给出定义:

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

节流(throttle)

继续思考,使用上面的防抖方案来处理问题的结果是:

  • 如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?(此处暂且不论哪种方案更合适,既然产品爸爸说话了我们就先考虑怎么实现)
图片描述

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}
/* 请注意,节流函数并不止上面这种实现方案,
   例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
    */

// 以下照旧
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 

运行以上代码的结果是:

  • 如果一直拖着滚动条进行滚动,那么会以1s的时间间隔,持续输出当前位置和顶部的距离

其他应用场景举例

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

  1. 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
  2. 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

思考总结

上述内容基于防抖和节流的核心思路设计了简单的实现算法,但是不代表实际的库(例如undercore js)的源码就直接是这样的,最起码的可以看出,在上述代码实现中,因为showTop本身的很简单,无需考虑作用域和参数传递,所以连apply都没有用到,实际上肯定还要考虑传递argument以及上下文环境(毕竟apply需要用到this对象)。这里的相关知识在本专栏《柯里化》和《this对象》的文章里也有提到。本文依然坚持突出核心代码,尽可能剥离无关功能点的思路行文因此不做赘述。


惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址

查看原文

赞 392 收藏 240 评论 52

皮皮盐 发布了文章 · 2020-09-04

WebStrom插件推荐

1、 Translation

非常好用的翻译插件,平时可以用作翻译变量或者函数名时使用,ctrl+shift+O快捷弹出,阅读源码的时候也可以选中右击快捷的翻译英文注释

2、One Dark them

Atom的看家主题,从vscode一直用到webstrom,逢人推荐,个人很喜欢的深色主题

3、Key Promoter X

想学习IDE的快捷键,但每次都习惯下意识的鼠标点击怎么办?安利一波Key Promoter X,每次点击IDE的某些带有快键键的按钮都会提示你,如何使用快键键操作

4、Atom Material Icons

Atom的自定义图标,有点类似于vscode的vscode-icons插件,替换了文件与IDE的各种图片,挺有意思的,安利了同事,同事们都说花里胡哨.....

5、Rainbow Bracket

彩虹花括号插件,个人觉得也很好看,可以很清晰的看出括号的匹配

6、CodeGlance

类似于vscode或者是sublime的代码右侧缩略图插件,尤其在代码特别长的文件中,特别能看出代码结构

6、Nyan Progress Bar

小猫进度条插件,老夫的少女心......(插件商店同样有马里奥、宝可梦、伊布、吃豆子等进度条)

查看原文

赞 1 收藏 0 评论 0

皮皮盐 回答了问题 · 2020-05-30

求高效简单数组 方法

arr1.push(...arr2.filter(item => !arr1.map(i => i.number).includes(item.number))

关注 5 回答 4

皮皮盐 关注了用户 · 2020-05-29

编程码农 @onlythinking

Life is so short, do something to make yourself happy, such as coding.

关注 544

认证与成就

  • 获得 3 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-05-29
个人主页被 229 人浏览