前端开发 – 有爱前端 http://gzui.net 贵在有爱 Mon, 11 Dec 2023 09:31:05 +0000 zh-CN hourly 1 https://wordpress.org/?v=5.8.9 价值一个亿的AI代码 http://gzui.net/archives/4353 Mon, 11 Dec 2023 09:28:43 +0000 http://gzui.net/?p=4353

提示:你可以先修改部分代码再运行。

转载请注明:有爱前端 » 价值一个亿的AI代码

]]>
手电筒效果 http://gzui.net/archives/4345 Wed, 06 Dec 2023 05:49:55 +0000 http://gzui.net/?p=4345 https://juejin.cn/post/7231015741453303845

提示:你可以先修改部分代码再运行。

https://blog.csdn.net/qq_56402474/article/details/132888664

提示:你可以先修改部分代码再运行。

转载请注明:有爱前端 » 手电筒效果

]]>
tabbar中间凹陷悬浮按钮 http://gzui.net/archives/4338 Thu, 26 Oct 2023 02:08:31 +0000 http://gzui.net/?p=4338 https://zhuanlan.zhihu.com/p/545750372

提示:你可以先修改部分代码再运行。

转载请注明:有爱前端 » tabbar中间凹陷悬浮按钮

]]>
背景图片变暗处理 http://gzui.net/archives/4335 Fri, 20 Oct 2023 07:46:29 +0000 http://gzui.net/?p=4335

提示:你可以先修改部分代码再运行。

转载请注明:有爱前端 » 背景图片变暗处理

]]>
IntersectionObserver http://gzui.net/archives/4329 Thu, 12 Oct 2023 03:04:34 +0000 http://gzui.net/?p=4329 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>IntersectionObserver</title> </head> <body style="font-size: 24px;text-align: center"> <div id="container"></div> <div id="loadMore">加载中...</div> </body> <script> const container = document.querySelector('#container'); const loadMore = document.querySelector('#loadMore'); let index = 0; const loadItems = (count) => { [...Array(count).keys()].forEach((key) => { const p = document.createElement('P'); p.innerHTML = `${key + index}`; container.appendChild(p) }) index += count; } const observer = new IntersectionObserver((entries) => { entries.forEach(({ isIntersecting }) => { if (isIntersecting) { loadItems(20); } }) }); observer.observe(loadMore) </script> </html>

转载请注明:有爱前端 » IntersectionObserver

]]>
canvas学习之canvas实现粒子动画效果 http://gzui.net/archives/4324 Wed, 11 Oct 2023 03:27:28 +0000 http://gzui.net/?p=4324 https://juejin.cn/post/7066828430267908109

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas实现粒子动画效果</title>
    <style>
        html {
            width: 100%;
            height: 100%;
        }

        body {
            height: 100%;
            width: 100%;
        }

        * {
            margin: 0;
            padding: 0;
        }
        #myCanvas{
            position: fixed;
            top: 0;
            left: 0;
        }
    </style>
</head>

<body>
    <canvas id="myCanvas" width=""></canvas>
    <script>
        // 获取canvas标签
        let canvas = document.getElementById("myCanvas");

        // 初始化画布的宽高,根据浏览器窗口的宽高来初始化,并且保存浏览器宽高(这里主要是为了后续在浏览器边缘的碰撞检测处使用)
        let width = (canvas.width = window.innerWidth);
        let height = (canvas.height = window.innerHeight);

        // 获取canvas上下文(画笔)
        let ctx = canvas.getContext("2d");

        // 工具函数
        function random(min, max) {
            let num = Math.floor(Math.random() * (max - min) + min);
            if (num === 0) {
                num = 1;
            }
            return num;
        }
        // 随机生成颜色 (rgb)
        function randomColor() {
            return `rgb(${random(0, 255)},${random(0, 255)},${random(0, 255)})`;
        }

        // 实现小球类
        /**
         * @param {number} x 横坐标
         * @param {number} y 纵坐标
         * @param {number} vx 沿横轴方向的速度 (可以为负数)
         * @param {number} vy 沿纵轴方向的速度(可为负数)
         * @param {number} size 小球的半径
         * @param {string} color 颜色
         * @param {string} line 线条颜色
         *
         */

        function Ball(x, y, vx, vy, size, color, line) {
            this.x = x;
            this.y = y;
            this.vx = vx;
            this.vy = vy;
            this.size = size;
            (this.color = color), (this.lineColor = line);
        }

        // 绘制当前小球对象的方法
        Ball.prototype.draw = function () {
            // console.log('this',this);
            // 开始绘制(路径)
            ctx.beginPath();
            // 设置画笔的填充颜色
            ctx.fillStyle = this.color;

            // 根据坐标绘制小球
            ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);

            // 填充
            ctx.fill();
        };

        // 更新小球的状态,让小球动起来
        Ball.prototype.update = function () {
            // 检查当前小球的X坐标加上当前的小球半径是否大于浏览器宽度(右边缘碰撞检测)
            // 检查当前小球的X坐标加上当前的小球半径是否小于0(左边缘碰撞检测)
            // 上述的两个条件如果满足其中一个,就需要调转方向(X轴速度vx设置为当前vx的相反数)
            if (this.x + this.size >= width || this.x - this.size <= 0) {
                this.vx = -this.vx;
            }

            // 检查当前小球的Y坐标加上当前的小球半径是否大于浏览器高度(下边缘碰撞检测)
            // 检查当前小球的Y坐标加上当前的小球半径是否小于0(上边缘碰撞检测)
            // 上述的两个条件如果满足其中一个,就需要调转方向(Y轴速度vy设置为当前vy的相反数)
            if (this.y + this.size >= height || this.y - this.size <= 0) {
                this.vy = -this.vy;
            }

            // 根据速度值改动小球的坐标
            this.x += this.vx;
            this.y += this.vy;
        };

        // 生成小球对象列表
        let list = [];
        for (let i = 0; i <= 90; i++) {
            let circle = new Ball(
                // x, y, vx, vy, size, color, line
                /**
                 * @param {number} x 横坐标
                 * @param {number} y 纵坐标
                 * @param {number} vx 沿横轴方向的速度 (可以为负数)
                 * @param {number} vy 沿纵轴方向的速度(可为负数)
                 * @param {number} size 小球的半径
                 * @param {string} color 颜色
                 * @param {string} line 线条颜色
                 *
                 */
                random(0, width),
                random(0, height),
                random(-6, 6) * (1 / 3.0),
                random(-6, 6) * (1 / 3.0),
                3,
                "rgb(255,255,255)",
                `rgba(${random(0, 255)},${random(0, 255)},${random(0, 255)}`
            );
            list.push(circle);
        }
        // console.log('list', list);

        // 循环函数
        function loopCircle() {
            // 刷新画布
            ctx.fillStyle = "rgba(0,0,0,0.6)";
            ctx.fillRect(0, 0, width, height);

            // 双重循环
            // 这里主要是为了计算小球之间的一个二维空间距离,性能不高,有很大的优化空间,读者有兴趣可以自行优化
            for (let i = 0; i < list.length; i++) {
                for (let j = 0; j < list.length; j++) {
                    // 计算当前小球的距离
                    let lx = list[j].x - list[i].x;
                    let ly = list[j].y - list[i].y;
                    let LL = Math.sqrt(Math.pow(lx, 2) + Math.pow(ly, 2));

                    //比对:当距离满足时,绘制线条,以rgba实现过渡
                    if (LL <= 180) {
                        ctx.beginPath();

                        // 这里补足了前面的半截rgba线条颜色
                        ctx.strokeStyle = `${list[i].lineColor},${(180 - LL) / 180})`;

                        // 绘制线条
                        ctx.moveTo(list[i].x, list[i].y);
                        ctx.lineWidth = 1;
                        ctx.lineTo(list[j].x, list[j].y);
                        ctx.stroke();
                    }
                }

                // 绘制小球
                list[i].draw();

                // 更新坐标
                list[i].update();
            }

            // 执行动画(这里传入函数自身后,该回调函数会在浏览器下一次重绘之前再次执行)
            requestAnimationFrame(loopCircle);
        }
        // 开始运行
        loopCircle();
    </script>
</body>

</html>

转载请注明:有爱前端 » canvas学习之canvas实现粒子动画效果

]]>
云朵 http://gzui.net/archives/4322 Sat, 07 Oct 2023 06:18:01 +0000 http://gzui.net/?p=4322

提示:你可以先修改部分代码再运行。

转载请注明:有爱前端 » 云朵

]]>
node将txt文件格式转为utf8 http://gzui.net/archives/4283 http://gzui.net/archives/4283#respond Fri, 04 Aug 2023 03:49:25 +0000 http://gzui.net/?p=4283 const fs = require('fs'); const path = require('path'); const jschardet = require('jschardet'); const iconv = require('iconv-lite'); // 指定需要处理的文件夹路径 const folderPath = 'your_folder_path'; // 递归遍历文件夹 function processFolder(folderPath) { fs.readdir(folderPath, (err, files) => { if (err) { console.error('读取文件夹失败:', err); return; } files.forEach(file => { const filePath = path.join(folderPath, file); fs.stat(filePath, (err, stat) => { if (err) { console.error('获取文件状态失败:', err); return; } if (stat.isFile()) { // 处理文件 processFile(filePath); } else if (stat.isDirectory()) { // 递归处理子文件夹 processFolder(filePath); } }); }); }); } // 处理文件 function processFile(filePath) { const extname = path.extname(filePath); if (extname !== '.txt') { console.log(filePath, '不是txt文件,跳过编码转换'); return; } fs.readFile(filePath, (err, data) => { if (err) { console.error('读取文件失败:', err); return; } // 检测文件编码 const detectedEncoding = jschardet.detect(data).encoding; // 判断文件是否为UTF-8编码 if (detectedEncoding.toLowerCase() === 'utf-8') { console.log(filePath, '已经是UTF-8编码'); return; } // 将文件内容从原编码转换为UTF-8 const content = iconv.decode(data, detectedEncoding); const utf8Content = iconv.encode(content, 'utf-8'); // 将转换后的内容写入文件 fs.writeFile(filePath, utf8Content, err => { if (err) { console.error('写入文件失败:', err); return; } console.log(filePath, '编码修改为UTF-8成功'); }); }); } // 处理指定的文件夹 processFolder(folderPath);

转载请注明:有爱前端 » node将txt文件格式转为utf8

]]>
http://gzui.net/archives/4283/feed 0
时间线 http://gzui.net/archives/4280 http://gzui.net/archives/4280#respond Thu, 27 Jul 2023 05:46:36 +0000 http://gzui.net/?p=4280

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- https://codepen.io/jkantner/pen/ZEqvpWd -->
  <title>时间线</title>
  <style>
    * {
    border: 0;
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
:root {
    --hue: 3;
    --bg: hsl(var(--hue),10%,90%);
    --fg: hsl(var(--hue),10%,10%);
    --primary: hsl(var(--hue),90%,50%);
    --primaryT: hsla(var(--hue),90%,50%,0);
    --trans-dur: 0.3s;
    font-size: calc(16px + (24 - 16) * (100vw - 320px) / (2560 - 320));
}
body {
    background-color: var(--bg);
    color: var(--fg);
    display: flex;
    font: 1em/1.5 "DM Sans", sans-serif;
    height: 100vh;
    transition:
        background-color var(--trans-dur),
        color var(--trans-dur);
}
a {
    transition: color var(--trans-dur);
}
h1 {
    font-size: 2em;
    margin: 0 0 1.5rem;
    text-align: center;
}
.timeline {
    margin: auto;
    padding: 3em 0;
}
.timeline__items {
    list-style: none;
    margin-left: 0.75em;
}
.timeline__item {
    padding: 0 0 1.5em 1.25em;
    position: relative;
}
.timeline__item br {
    display: none;
}
.timeline__item:before,
.timeline__item:after,
.timeline__item .timeline__item-pub,
.timeline__item .timeline__item-time,
.timeline__item .timeline__item-link {
    transition:
        background-color var(--trans-dur),
        opacity var(--trans-dur) cubic-bezier(0.65,0,0.35,1),
        transform var(--trans-dur) cubic-bezier(0.65,0,0.35,1);
}
.timeline__item:before,
.timeline__item:after {
    background-color: var(--primary);
    content: "";
    display: block;
    position: absolute;
    left: 0;
}
.timeline__item:before {
    border-radius: 50%;
    top: 0.25em;
    width: 1em;
    height: 1em;
    transform: translateX(-50%) scale(0);
    z-index: 1;
}
.timeline__item:after {
    top: 0.75em;
    width: 0.125em;
    height: 100%;
    transform: translateX(-50%);
}
.timeline__item:last-child:after {
    display: none;
}
.timeline__item-pub,
.timeline__item-link,
.timeline__item-time {
    display: block;
    opacity: 0;
    transform: translateX(-0.75em);
}
.timeline__item-link,
.timeline__item-link:visited {
    color: var(--primary);
}
.timeline__item-link {
    transition: color var(--trans-dur);
}
.timeline__item-link:hover {
    text-decoration: underline;
}
.timeline__item-pub,
.timeline__item-time {
    font-size: 0.833em;
    line-height: 1.8;
}
.timeline__item-time {
    font-weight: bold;
}
.timeline__loading {
    text-align: center;
}

/* Observed items */
.timeline__item--in .timeline__item-pub,
.timeline__item--in .timeline__item-link,
.timeline__item--in .timeline__item-time {
    opacity: 1;
    transform: translateX(0);
}
.timeline__item--in:before {
    transform: translateX(-50%) scale(1);
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
    :root {
        --bg: hsl(var(--hue),10%,10%);
        --fg: hsl(var(--hue),10%,90%);
        --primary: hsl(var(--hue),90%,70%);
    }
}

/* Beyond mobile */
@media (min-width: 768px) {
    .timeline__items {
        margin-left: 17em;
        width: 17em;
    }
    .timeline__item-time {
        position: absolute;
        top: 0;
        right: calc(100% + 1.25rem);
        text-align: right;
        width: 17rem;
        transform: translateX(0.75em);
    }
}
  </style>
</head>

<body>
  <div class="timeline">
    <h1>Writing</h1>
    <ul class="timeline__items" data-items></ul>
    <div class="timeline__loading" data-loading>Loading timeline…</div>
  </div>
  <script type="module">
    import { faker } from "https://cdn.skypack.dev/@faker-js/faker@7.6.0"

      window.addEventListener("DOMContentLoaded",() => {
        const t = new Timeline(".timeline");
      });

      class Timeline {
        articles = [];

        constructor(el) {
          this.el = document.querySelector(el);

          this.init();
        }
        init() {
          this.generateArticles();
          this.removeLoading();
          this.build();
          this.observeArticles();
        }
        build() {
          const itemContainer = this.el.querySelector("[data-items]");
          if (!itemContainer) return;

          const localeCode = "en-US";

          this.articles.forEach(article => {
            const time = document.createElement("time");
            time.className = "timeline__item-time";

            const DRaw = new Date(article.date);
            const D = {
              y: DRaw.getFullYear(),
              m: DRaw.getMonth() + 1,
              d: DRaw.getDate()
            };

            if (D.m < 10) D.m = `0${D.m}`;
            if (D.d < 10) D.d = `0${D.d}`;

            time.setAttribute("datetime", `${D.y}-${D.m}-${D.d}`);

            const articleDateLocal = DRaw.toLocaleDateString(localeCode,{
              year: "numeric",
              month: "long",
              day: "numeric"
            });
            time.innerText = articleDateLocal;

            const link = document.createElement("a");
            link.className = "timeline__item-link";
            link.href = "#";
            link.innerText = article.title;

            const published = document.createElement("small");
            published.className = "timeline__item-pub";
            published.innerText = article.publisher;

            const item = document.createElement("li");
            item.className = "timeline__item";
            item.appendChild(time);
            item.appendChild(document.createElement("br"));
            item.appendChild(link);
            item.appendChild(document.createElement("br"));
            item.appendChild(published);

            itemContainer.appendChild(item);
          });
        }
        generateArticles() {
          const articleCount = 30;

          try {
            for (let a = 0; a < articleCount; ++a) {
              const adjective = faker.company.catchPhraseAdjective();
              const noun = faker.company.catchPhraseNoun();

              this.articles.push({
                title: this.toTitleCase(`${adjective} ${noun}`),
                date: faker.date.past(10),
                publisher: faker.company.name(),
              });
            }
          } catch (err) {
            console.log("Faker unavailable");
          }
          // reverse chronological order
          this.articles.sort((a,b) => b.date - a.date);
        }
        observeArticles() {
          this.observer = new IntersectionObserver(
            entries => { 
              entries.forEach(entry => {
                const { target } = entry;
                const itemIn = "timeline__item--in";

                if (entry.isIntersecting) target.classList.add(itemIn);
                else target.classList.remove(itemIn);
              });
            }, {
              root: null,
              rootMargin: "0px",
              threshold: 1
            }
          );
          // observe the items
          const items = document.querySelectorAll(".timeline__item");
          Array.from(items).forEach(item => {
            this.observer.observe(item);
          });
        }
        removeLoading() {
          const loading = this.el.querySelector("[data-loading]");
          if (!loading) return;

          this.el.removeChild(loading);
        }
        toTitleCase(title) {
          return title.split(" ").map(word => word[0].toUpperCase() + word.substring(1)).join(" ");
        }
      }
  </script>
</body>

</html>

转载请注明:有爱前端 » 时间线

]]>
http://gzui.net/archives/4280/feed 0
前端图片压缩上传,减少等待时间!优化用户体检 http://gzui.net/archives/4276 http://gzui.net/archives/4276#respond Fri, 21 Jul 2023 07:55:58 +0000 http://gzui.net/?p=4276 https://mp.weixin.qq.com/s/Cm-7teN8f9RkBL8rP9FHnw

这里有两张图片,它们表面看上去是一模一样的,但实际上各自所占用的内存大小相差了180倍。

图片

图片

可以看到右边的图片是22.3MB,而左侧的图片只有127KB,但是实际上这两张图片的大小都是22.3MB。

最近在开发中遇到这样的一个需求,需要把用户上传的图片先进行一次压缩,然后再保存到服务器,这里我们除了优先考虑压缩图片的大小外,还要顾及图片压缩后的清晰度问题。

图片

经过对比,图片并没有明显的失真情况,下面给大家分享一下,我的解决方法:

这里我采用element的文件上传控件来上传图片


<el-upload
  class="avatar-uploader"
  :action="GLOBAL.serverFileUrl"
  name="file"
  drag
  :show-file-list="false"
  :on-change="beforeAvatarUpload"
 >
  <i class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>

定义了三个事件方法

//图片上传之前处理事件
  beforeAvatarUpload(file) {
    console.log(file);
    const isJpgPng =
        file.raw.type === "image/jpeg" || file.raw.type === "image/png";
    if (!isJpgPng ) {
      this.GLOBAL.messageEvent("error","上传头像图片只能是 JPG/PNG 格式!");
    } else {
        this.compressImg(file.raw);
    }
    return isJpgPng;
  },

compressImg(file) {
      let that = this;
      // ?通过FormData构造函数创建一个空对象
      let formData = new FormData();
      let reader = new FileReader();
      // ?将读取到的文件编码成DataURL
      reader.readAsDataURL(file);
      // ?压缩图片
      reader.onload = function(ev) {
         try {
             // ?读取图片来获得上传图片的宽高
             let img = new Image();
             img.src = ev.target.result;
             img.onload = function(ev) {
             // ?将图片绘制到canvas画布上进行压缩
             let canvas = document.createElement("canvas");
             let context = canvas.getContext("2d");
             let imgwidth = img.width;
             let imgHeight = img.height;
             // ?按比例缩放后图片宽高;
             let targetwidth = imgwidth;
             let targetHeight = imgHeight;
             // ?/如果原图宽大于最大宽度
             if (targetWidth > targetHeight) {
                  // ?原图宽高比例
                  let scale = targetHeight / 1280;
                  targetHeight = 1280;
                  targetWidth = targetwidth / scale;
              } else {
                    // ?原图宽高比例
                    let scale = targetWidth / 1280;
                    targetWidth = 1280;
                    targetHeight = targetHeight / scale;
               }
               // ?缩放后高度仍然大于最大高度继续按比例缩小
               canvas.width = targetwidth; //canvas的宽=图片的宽
               canvas.height = targetHeight; //canvas的高=图片的高
               context.clearRect(0,0, canvas.width, canvas.height);
               context.drawImage(this, 0, 0, canvas.width, canvas.height);
               let data = "":
               // ?如果图片小于0.6Mb,不进行压缩,并返回二进制流
               if (file.size <= 628288) {
                   data = canvas.toDataURL("image/jpeg");
                   formData.append("file", file);
                   that.handleChange(file);
                }
                // ?如果图片大于e.6Mb,进行压缩,并返回二进制流
                else {
                   // todo 压缩文件大小比例
                   data = canvas.toDataURL("image/jpeg",0.4);
                   let paper = that.GLOBAL.dataURLtoFile(data, file.name);
                   formData.append("file", paper);
                   that.handleChange(paper);
                }
              };
            } catch (error) {
                  console.log("出现错误",error);
             }
         };
      },
  // todo 调用上传接口 文件提交给后台
      handleChange(file) (
          let formData = new FormData( );
          formData.append("file",file.raw || file);
          console.log(formData);
          brandServices.uploadFile(formData).then(res => {
             if (res.data.errno === 0) {
                 this.imgUrl = res.data.data;
                 this.dialogImageUrl = URL.createObjectURL(file);
                 this.GLOBAL.messageEvent("success",res.data.message);
             }  else { 
                  this.GLOBAL .messageEvent("error",res .data.message);
              }
           });
       }

总结:

先进行图片上传前的验证;接着再对图片实现压缩的操作;最后就可以把文件流提交给后台。

具体的思路是:通过FormData构造函数创建一个空对象,将图片绘制到canvas画布上,然后再进行压缩。用户上传的文件超过一定的大小后就可以执行压缩的操作,当然如果图片太小的话,我们就没必要再压了。建议采用宽高等比例的方式来压缩,不然可能会出现图片变形的情况。

转载请注明:有爱前端 » 前端图片压缩上传,减少等待时间!优化用户体检

]]>
http://gzui.net/archives/4276/feed 0