阅读视图

发现新文章,点击刷新页面。

实现一个文字擦除效果

前言

动画实际上就是一个视觉欺骗功能,一个前端如果不会视觉欺骗,那么针对很多效果将会手无足措,实际上学习动画的过程中,也能更好理解视觉欺骗,实际开发一些场景也会有更多实现方案

这里的擦除效果,就是利用视觉欺诈实现一个文字擦除效果(前面写到的进度条也有视觉欺骗在里面)

核心原理,就是在文本上方放置同样的一个文本内容,设置颜色将其盖住,通过css属性动画逐步放开遮挡区间,这样看起来就像是文本逐渐展示了(实现是不是和看起来效果有点不太一样,这就是视觉欺骗了)

实现

import "./App.css";

//基础文本
const text = `
“再也不能骂人了!”近日,一条以“骂人也违法了
最高可判三年”为关键词的话题登上热搜,迅速引发公众热议。这背后,是自2026年1月1日起正式施行的新修订《治安管理处罚法》,言语威胁、辱骂他人可能构成违法!公然辱骂他人或捏造事实,造成重伤或死亡的还涉嫌犯罪。请谨言慎行,莫因“口无遮拦”承担法律责任。
法律依据 《中华人民共和国治安管理处罚法》(2026),第五十条规定:
有下列行为之一的,处五日以下拘留或者一千元以下罚款;情节较重的,处五日以上十日以下拘留,可以并处一千元以下罚款:
(一)写恐吓信或者以其他方法威胁他人人身安全的;
(二)公然侮辱他人或者捏造事实诽谤他人的;
(三)捏造事实诬告陷害他人,企图使他人受到刑事追究或受到治安管理处罚的;
(四)对证人及其近亲属进行威胁、侮辱、殴打或者打击报复的;
(五)多次发送淫秽、侮辱、恐吓等信息或者采取滋扰、纠缠、跟踪等方法,干扰他人正常生活的;
(六)偷窥、偷拍、窃听、散布他人隐私的。
有前款第五项规定的滋扰、纠缠、跟踪行为的,除依照前款规定给予处罚外,经公安机关负责人批准,可以责令其一定期限内禁止接触被侵害人。对违反禁止接触规定的,处五日以上十日以下拘留,可以并处一千元以下罚款。
《中华人民共和国刑法》第二百四十六条:
以暴力或者其他方法公然侮辱他人或者捏造事实诽谤他人,情节严重的,处三年以下有期徒刑、拘役、管制或者剥夺政治权利。
`;

function App() {
  return (
    //使用两个一摸一样的文本效果,这样更好盖住
    //为了能重合使用绝对定位来处理
    <p className="App">
      <p className="box">
        {text}
      </p>
      <p className="box">
        //不适用匿名盒子,是为了更好操作该行盒
        <span className="earser">{text}</span>
      </p>
    </p>
  );
}

css实现,除了使用行盒子实现背景的逐行变化,这里借助css自定义属性,来实现动画(毕竟动画只能应用css内置属性,不能直接应用变量)

ps:当然使用 background-position属性,就不用自定义属性了,容我装一下哈😄

.App {
  margin: 0;
  color: black;
  position: relative;
  display: block;
  color: #000;
}

.box {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

@property --per {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 5%;
}

//使用行盒子设置背景,才能达到我们想要的效果
.earser {
  --per: 5%;
  color: transparent;
  background: linear-gradient(to right, transparent var(--per), #fff calc(var(--per) + 100px));
  animation
  : earser 5s linear forwards;
}
//关键帧动画
@keyframes earser {
  to {
    --per: 100%;
  }
}

这是一个简易的部分擦除效果(gif懒得生成)

image.png

最后

思考一下,如果文本区域存在一个背景图的话,那么这种遮盖方式可能就不行了,这种情况怎么实现文字擦除效果么,不妨思考一下

手写 Promise.all

前言

Promise 是 JavaScript 中非常重要的一个功能,他可以让我们异步的去处理一些事情,尤其是对于一些比较耗时的操作,其非常重要,当然一些不耗时的操作,有事就没必要使用它了,毕竟会带来额外的性能开销

Promise.all

Promise.all 是 js 中个一个静态方法,尤其是写业务对接后端的时候,会经常见到,不管是写后端异步签名,还是前端接口请求,其主要用来聚合一组成功的结果

相比于 promise 其他系列,这个算是比较简单了(当然 race 更简单就不介绍了)

下面简单陈述 Promise.all 手写的几个关键单

  1. all 是一个静态方法,因此不能像写对象的原型链 prototype一样写
  2. promise 状态从 pending 变更为 fulfilled 或者 rejected 只能发生一次,因此无需考虑多次回调 reject 的问题
  3. 遍历 promises 数组的时候,遍历的是所有可迭代对象,因此 for...of 非常适合

tips: 可以通过 promise 函数回调赋值的方式减少嵌套层级

//声明一个静态方法
Promise.myAll = function(promises) {
    let resolve, reject;
    //构造一个新的promise,赋值可以减少嵌套层级
    const completePromise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    })
    const results = [];
    const length = promises.length;
    let completedCount = 0;
    let idx = 0
    //tips: promise 状态从 pending 变更为 fulfilled 或者 rejected 只能发生一次
    for (const item of promises) {
        //使用一个临时变量接收 idx 避免补货到外部同一个索引
        const currentIndex = idx;
        //item可能不是promise,所以用Promise.resolve包一下
        Promise.resolve(item).then(value => {
            results[currentIndex] = value;
            completedCount++;
            //全部成功标记成功
            if (completedCount === length) {
                resolve(results);
            }
        }).catch(err => {
            //有一个返回err就行了,promise状态只会变更一次
            reject(err);
        })
        idx++;
    }
    return completePromise;
}

下面测试一下结果,非常正确


const test = () => {
    const p1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve(1)
        }, 1000)
    });
    const p2 = new Promise((resolve) => {
        setTimeout(() => {
            resolve(2)
        }, 2000)
    });
    const p3 = new Promise((resolve, reject) => {
        setTimeout(() => {
            // reject('error 3')
            resolve(3)
        }, 1500)
    });
    Promise.myAll([p1, p2, p3]).then(res => {
        console.log('res', res)
    }).catch(err => {
        console.log('err', err)
    })
}

test();

这篇文章就讲到这里了,由于内容相对简单,就不多介绍了

写一个简易的数字转中文功能

前言

今天我们写一个一个简易的数字转中文的效果,虽然是功能看起来很简单,但是要是没有写过这类功能的话,那么可能会无从下手,下面就简单讲一下写这类逻辑的过程

思路

实现简易思路:

  1. 大单位是以万、亿、兆等为单位的,4位小数一组,因此需要对数字进行四位一组分组操作,在后面追加单位即可
  2. 除了大单位,中间的小单位则是个十百千,基本都是一致,因此使用同一种统一处理方案,其中个位没有单位
  3. 除了单位还有每一位数字的翻译工作,即:0~9 => 零~九
  4. 淡出处理中文语法中特有的情况,例如:开头不显示一十而是十,零中间作为间隔只能有一个,连续的零没有大单位,以及各位为零的处理等
  5. 入参处理(这里简单处理一下)

代码实现

//数字转中文(要支持小数,可以在扩展,小数就比较简单了)
//入参为数字,因此不考虑开头为零的情
const convertChineseNumber = (number) => {
  if (isNaN(number)) return "NAN";

  const units = ["", "万", "亿", "兆", "京", "垓"];
  const baseUnits = ["", "十", "百", "千"];
  const cNumbers = ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"];
  
  //分割四位一组,反转数组,从后往前加单位
  const numGroup = String(number)
    .replace(/\B(?=(\d{4})+$)/g, " ")  //使用前瞻运算符来处理四位一体
    .split(" ")
    .reverse();
  let result = "";
  //遍历从后往前加单位
  numGroup.forEach((numStrings, idx) => {
    //处理全零的情况,不应该带单位,由于需要间隔0的情况,保留一个零
    if (numStrings === "0000") {
      result = '零' + result
      return;
    }
    //有数的先添加大单位
    result = units[idx] + result;
    //分割四位一组的小数字,仍然是反转,从后往前加单位
    numStrings
      .split("")
      .reverse()
      .forEach((item, idx) => {
        //处理每个后面的单位,其中个位没有单位,0也没有单位
        if (idx !== 0 && item !== "0") {
          result = baseUnits[idx] + result;
        }
        //数字翻译
        result = cNumbers[Number(item)] + result;
      });
  });
  //处理结果中可能存在的中文读写问题,例如不会存在连续的零,开头不会一十
  //零后面不应该有单位,非零整数结尾不会读零
  return result
    .replace(/零+/g, "零")
    .replace(/^(一十)/, "十")
    .replace(new RegExp(`零([${units.join("")}]{1})`, "g"), "$1") //小单位处理了大单位也要处理
    .replace(/(.+)零$/, "$1")
};

测试案例

//我们试验几个案例
const numbers = [
  convertChineseNumber(0),
  convertChineseNumber(1234567899),
  convertChineseNumber(101234567899),
  convertChineseNumber(100234567899),
  convertChineseNumber(101034567899),
  convertChineseNumber(101030067899),
  convertChineseNumber(101030067890),
  convertChineseNumber(101030007899),
  convertChineseNumber(100000007899),
  convertChineseNumber(100070007899),
  convertChineseNumber(100007007899),
];
console.log(numbers);

//目前来看返回结果是对的,如果感觉哪里不对,可以在填填补补就行了
[
  '零',
  '十二亿三千四百五十六万七千八百九十九',
  '一千零一十二亿三千四百五十六万七千八百九十九',
  '一千零二亿三千四百五十六万七千八百九十九',
  '一千零一十亿三千四百五十六万七千八百九十九',
  '一千零一十亿三千零六万七千八百九十九',
  '一千零一十亿三千零六万七千八百九十',
  '一千零一十亿三千万七千八百九十九',
  '一千亿零七千八百九十九',
  '一千亿七千万七千八百九十九',
  '一千亿零七百万七千八百九十九'
]

最后

上面的整体思路还是可以得,实际还是有所欠缺的,例如:不支持小数,对于大数字的单位支持也不够多,甚至一些读法的场景可能考虑还不够完善

需要完善的话,实际按照上面思路补充一下就行了

本篇就介绍到这里了

script标签有哪些常用属性,作用分别是啥

前言

前端开发中,离不开的就是script脚本的执行,html中嵌入script标签,我们的js脚本才能正常执行,正是因为有了它,前端的页面变得更加灵活了(不是纯静态页面了)

简介

常用属性:

src:脚本引用资源地址,可以是远端,也可以是本地路径的资源

<script src="https://example.com/script.js"></script>

<script src="./common.js"></script>

type: script类型,HTML5 默认为 text/javascript,也基本上很少主动设置了,为了兼容性可以设置

crossorigin:跨域属性 "anonymous"(匿名)、"use-credentials"(使用身份令牌验证)

<script src="https://example.com/script.js" crossorigin="anonymous"></script>

async: 异步(延迟)执行脚本

  • 脚本下载时不阻塞 HTML 解析

  • 下载完成后立即执行

  • 执行顺序不保证

defer: 异步(延迟)执行脚本

  • 脚本下载时不阻塞 HTML 解析

  • HTML 解析完成后才执行

  • 按顺序执行多个 defer 脚本

integrity:子资源完整性验证,防止恶意更改文件来执行潜在攻击

例如:script可能来自cdn提速,但是为了避免被人篡改过,于是使用算法加密,提前设定好脚本加密后的结果,浏览器下载后会将资源加密后进行对比,一致时执行,可以减少被攻击的可能

常用算法有 `sha256``sha384``sha512`(不推荐 md5/sha1,安全性低)。

<script 
    src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" 
    integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" 
    crossorigin="anonymous"
    />
    

生成sha256哈希值

cat script.js | openssl dgst -sha256 -binary | base64 # 生成sha384哈希值

最后

实际常用的就这么多,且前几个最常用(老代码中比较常见)

使用 && 整合事件,简化代码

前言

js 中 && 和 || 使用好了,有时候能够将我们的代码写的更舒服,甚至逻辑更加清晰,合理的利用他们,能让我们的代码看起来含金量更高一些

ps:当然不一定是真的含金量高很多,如果对于用习惯的来说,确实逻辑更紧密了,个人减少了很多代码和多余判断😄

&& 是返回符合条件的最后一项或不符合的第一项 || 是返回符合条件的第一项或不符合的最后一项

这里就使用 && 举个案例, || 相信遇到合适场景,自己就知道咋回事了

案例

我有一个弹窗,点击确定后,将选中内容后回调给外部,否则提示请选择数据

让ai帮我写这一块代码,是这样的,简单粗暴易懂

//pageInfo 和 onOk 已经定义好了
const onComplete = () => {
  const { dataSource, key } = pageInfo;
  if (dataSource && dataSource.length > 0 && key) {
    return Modal.info({
      title: '信息',
      content: '请选择一条数据',
    });
  }
  const record = dataSource.find(item => item.key === key);
  if (!record) {
    return Modal.info({
      title: '信息',
      content: '请选择一条数据',
    });
  }
  onOk && onOk(record);
}

下面我是这么写的,不想多写提示了,合理利用 && 会返回符合条件最后一项特性来实现,看起来似乎好了一点点

//pageInfo 和 onOk 已经定义好了
const onComplete = () => {
  const { dataSource, key } = pageInfo;
  const hasSelectedData =
    dataSource &&
    dataSource.length > 0 &&
    key &&
    dataSource.find((item) => item.key === key);  
  if (!hasSelectedData) {
    return Modal.info({
      title: "信息",
      content: "请选择一条数据",
    });
  }
  onOk && onOk(ifs.pop());
};

最后

普通的代码蕴含着我们对于代码的一些追求,你觉得什么样的代码好呢,反正我的ai感觉我的代码是屎,给我改了😄

❌