知识与经验,探索和发现(壹)

2021/05/25 知识与经验,探索和发现 本文共14809字,阅读全文约需51分钟 本文总阅读量

缘起

ARTS 的终止

  前端领域飞速发展,世界风云波谲云诡。学习资料堆积如山难以选择;计划总是跟不上变化。那么,作为前端浪潮中的弄潮儿,我们该如何选择,才能适应时代的变化呢?我想,我们也许可以做如下思考:

  • 哪些事情做起来一定有益于个人的发展,如此,可避免总是在变化中无序选择;
  • 只要一直做正确的事情,一直有经验的沉淀与思考,多年后的某一天,你会惊异地发现自己已经获得了超出预期的成长;
  • 定点捕捞比广撒网更能获取你想要的知识。

  同时,我认为,ARTS 打卡计划:

  • 是职业生涯特定阶段的产物,比如它重算法、力求提升个人技术影响力;
  • 以前的 ARTS 打卡,主要坚持的可能也只是 ATS,R 很少,实际上在某种程度上也难以发挥它的最大价值;
  • 需要用这些时间系统性的干一些更重要的事儿,不仅仅是现阶段更重要的事儿;
  • 知识积累过于宽泛,难有特定领域的深入探索和沉淀、产出;
  • 希望能一直做正确的事情,一直有经验的沉淀与思考,获得更多有深度的沉淀

新的开始

  • 周期:两周 ~ 两个月为一个周期
  • 主题:知识与经验,探索和发现;
  • 形式:
    • Knowledge and Experience【阅读、识见、思考,不仅仅局限于专业知识,也包括生活、人生等】
      • 最近两周学会的技术、小技巧和收获(可以含以前的算法)
      • 技术文章阅读学习
      • 格局、价值观、成长、人生类
      • 财经、理财类
      • 生活、运动健康等
      • 不定期的实践增补
    • Exploration and Discovery【探索与发现】
      • 特定领域的技术和知识沉淀【定点捕捞】
      • 技术文章输出,建议两周一篇
      • 领悟与收获
      • 不定期的实践增补

  两年为期,遇见更好的自己! —— 2021.05.24 By Cheney。

立春

截止 2021.07.23

Knowledge and Experience

  • background-image问题
.body {
  overflow: hidden; /* 这个可以禁止下拉和左右滑动 */
  /* overflow-x: hidden; // 限制左右滑动位移 */
  background: #201d32; /* 不设置background的话,会有白边,下拉会漏出白底 */
  background-image: linear-gradient(to bottom, #201d32, #000000 100%);
}
  • 移动端 web 禁止长按选择文字以及弹出菜单
/*如果是禁用长按选择文字功能,用css*/
* {
  user-select: none;
}
// 如果是想禁用长按弹出菜单, 用js
window.addEventListener('contextmenu', function (e) {
  e.preventDefault();
});
  • pointer-events是 css3 的一个属性,指定在什么情况下元素可以成为鼠标事件的target(包括鼠标的样式)。pointer-events属性有很多值,但是对于浏览器来说,只有auto(默认值)和none两个值可用,其它的几个是针对 SVG 的(本身这个属性就来自于 SVG 技术)。none值时,元素永远不会成为鼠标事件的target(目标)。
  • 不要过度使用 React.useCallback()
  • git 对比两个分支差异
  git diff branch1 branch2 --stat // 显示出branch1和branch2中差异的部分
  git diff branch1 branch2 具体文件路径 // 显示指定文件的详细差异
  git diff branch1 branch2 // 显示出所有有差异的文件的详细差异
  git log branch1 ^branch2 // 查看branch1分支有,而branch2中没有的log
  git log branch1..branch2 // 查看branch2中比branch1中多提交了哪些内容。注意,列出来的是两个点后边(此处即dev)多提交的内容。
  git log branch1...branch2 // 不知道谁提交的多谁提交的少,单纯想知道有什么不一样
  git log -left-right branch1...branch2 // 在上述情况下,在显示出每个提交是在哪个分支上。注意 commit 后面的箭头,根据我们在 –left-right branch1…branch2 的顺序,左箭头 < 表示是 branch1 的,右箭头 > 表示是branch2的。
  • opacity 子元素继承父元素透明度的解决方法(参考
    • 父元素背景颜色设置透明度时,避免使用 background:#000;opacity:0.5,建议使用 background:rgba(0,0,0,0.5)
    • 如果设置背景色为渐变色等这种复杂背景,子元素会继承父元素的 opacity 属性,我们让它不成为子元素。新增一个子元素,将其绝对定位到父元素位置,然后在该元素上设置背景色与透明度。
  • fixed 元素抖动问题
  • 长背景,上部白底,下底有背景:background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), #000000 100%);;
  • CSS 实现渐隐渐现效果
  • git cherry-pick 教程git cherry-pick命令的作用,就是将指定的提交(commit)应用于其他分支。【修改错了分支,已经 commit,但是又不能直接合并分支】

    $ git cherry-pick <commitHash> # 将指定的提交commitHash,应用于当前分支
    $ git cherry-pick feature # 将feature分支的最近一次提交,转移到当前分支
    $ git cherry-pick <HashA> <HashB> # 将 A 和 B 两个提交应用到当前分支
    
  • 0.5px 边框问题(部分三星机型):CSS 0.5px 细线边框的原理和实现方式

    要实现小于 1px 的线条,有个先决条件:屏幕的分辨率要足够高,设备像素比要大于 1,即 css 中的 1 个像素对应物理屏幕中 1 个以上的像素点。

    // border-width: 0.5px;
    border-width: 1px;
    transform: scaleY(0.5);
    
  • rgba 的另两种写法:background: rgba($color: #10152a, $alpha: 0.1);background: rgba(red, green, blue, alpha)

  • 过滤字符串中的表情:
// emoji 范围
const emojiRanges = [
  '\ud83c[\udf00-\udfff]',
  '\ud83d[\udc00-\ude4f]',
  '\ud83d[\ude80-\udeff]',
];

// emoji 正则
const emojiReg = new RegExp(emojiRanges.join('|'), 'g');

// 过滤掉表情
export const filterEmoji = (str: string): string => {
  return str.replace(emojiReg, '');
};

Exploration and Discovery

前端性能优化

走进浏览器的世界

雨水

截止 2021.09.23

Knowledge and Experience

/**
  * 定义循环方法
  * @index--传入的循环起始值
*/
.LoopTransform(@index) when(@index<6) {
  // 执行内容
  // 类名参数要加大括号@{index}
  // 根据index获取对应的某个值
  .swiper-wrapper .swiper-slide:nth-child(@{index}) {
    /*index号图像向前方移位300px*/
    // 注意,这里不能写成(72 * (@index - 1))deg
    transform: rotateY(72deg * (@index - 1)) translateZ(300px);
  }

  //递归调用 达到循环目的
  .LoopTransform(@index+1);
}

// 调用循环
.LoopTransform(1);

透明度对照表

// 转换方法
const arrHex = [
  '0',
  '1',
  '2',
  '3',
  '4',
  '5',
  '6',
  '7',
  '8',
  '9',
  'A',
  'B',
  'C',
  'D',
  'E',
  'F',
]; //十六进制数组

/**
 * 将颜色值转为带透明度的16进制
 * @param param0
 * @returns
 */
export const getHexOpacityColor = ({ color = '#000000', opacity = 1 }) => {
  color = color.replace(/\#/g, '').toUpperCase();
  if (color.length === 3) {
    const arr = color.split('');
    color = '';
    for (let i = 0; i < arr.length; i++) {
      color += arr[i] + arr[i]; //将简写的3位字符补全到6位字符
    }
  }
  const opacityStr = getHexOpacity({ opacity });
  return `#${color + opacityStr}`;
};

/**
 * 透明度转16进制表示
 * @param param0
 * @returns
 */
export const getHexOpacity = ({ opacity = 0.5 }) => {
  opacity = Math.max(opacity, 0);
  opacity = Math.min(opacity, 1);
  let num = Math.round(255 * opacity); //向下取整
  // let str = num.toString(16); // 可以用这一行替代下面的循环
  let str = '';
  while (num > 0) {
    const mod = num % 16;
    num = (num - mod) / 16;
    str = arrHex[mod] + str;
  }
  if (str.length == 1) str = '0' + str;
  if (str.length == 0) str = '00';
  return `${str}`;
};

/**
 * 16进制透明度转小数
 * @param param0
 * @returns
 */
export const getDecimalOpacity = ({ hexOpacity = '00' }) => {
  let opacity = Number((parseInt(hexOpacity, 16) / 255).toFixed(2));
  opacity = Math.max(opacity, 0);
  opacity = Math.min(opacity, 1);
  return opacity;
};

// 16进制转RGB值
export const hexToRgb = (hex: string) => {
  hex = hex.replace(/\#/g, '').toUpperCase();
  if (hex.length === 3) {
    const arr = hex.split('');
    hex = '';
    for (let i = 0; i < arr.length; i++) {
      hex += arr[i] + arr[i]; //将简写的3位字符补全到6位字符
    }
  }
  const bigint = parseInt(hex, 16);
  return {
    r: (bigint >> 16) & 255,
    g: (bigint >> 8) & 255,
    b: bigint & 255,
  };
};

// RGB值转16进制
// 其实 ((r << 16) + (g << 8) + b).toString(16)已经可以了,为什么前边还要加个 (1 << 24) 再做处理
// 解释:为了防止 r,g,b值全为 0 的特殊情况, ((1 << 24))的值二进制表示为 100...0(1后边有24个0),加上r(0),g(0),b(0),结果不变, ((1 << 24)).toString(16) 的值为 "1000000"
export const rgbToHex = ({ r, g, b }: { r: number, g: number, b: number }) => {
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};

/**
 * 客户端xml色值转css
 * 8位16进制色值,客户端透明度在前,css透明度在后
 * @param xml
 * @returns
 */
export const xmlColor2CSSColor = (xml: string = '') => {
  if (xml.length === 7) xml = '#FF' + xml.substring(1);
  return xml.substring(0, 1) + xml.substring(3) + xml.substring(1, 3);
};

/**
 * css色值转客户端xml色值
 * @param css
 * @returns
 */
export const cssColor2XMLColor = (css: string = '') => {
  if (css.length === 7) css += 'FF';
  return css.substring(0, 1) + css.substring(7) + css.substring(1, 7);
};
  • 使用通配符的属性选择器【E[Attr^=Val]】【E[Attr$=Val]】【E[Attr*=Val]:css3 属性选择器中的大 boss,使得选择器功能分分钟提升。

    • E[att^="val"],选择器匹配元素 E,且 E 元素定义了属性 att,att 的属性值是以 val 开头的任何字符串。
    • E[att$="val"],选择器匹配元素 E,且 E 元素定义了属性 att,att 的属性值以 val 结尾的任何字符串。
    • E[att*="val"],选择器匹配元素 E,且元素定义了属性 att,att 属性值任意位置包含了”val”。
  • Mac 查看端口号是否被占用及释放

    • 提示信息:Something is already running on port 8080. Use port 8081 instead.
    • 查看使用端口进程:lsof -i: 端口号
    • 释放进程:kill 你的PID
    • 再次执行第一步,是否无进程占用:lsof -i: 端口号
  • Support for password authentication was removed. Please use a personal access token instead [duplicate] | Token authentication requirements for Git operations

    在 GitHub 创建个人 Access Token:从你的 Github 帐户,转到 Settings => Developer Settings => Personal Access Token => Generate New Token (Give your password) => Fill up the form => 单击 Generate token => Copy the generated Token,它将类似于 ghp_sFhFsSHhTzMDreGRLjmks4Tzuzgthdvfsrta【生成 Token 时记得进行选择 Token 的权限Creating a personal access token,否则 push 的时候可能会 403】

    对于 MAC 操作系统,单击菜单栏右侧的 Spotlight 图标(放大镜,或者 command + 空格)。键入 Keychain access 然后按 Enter 键启动应用程序【钥匙串访问】 => 在 Keychain Access 中,搜索 github.com=> 查找 Internet 密码条目 github.com=> 相应地编辑或删除该条目 => 大功告成

    对于 Windows 操作系统,从控制面板转到 Credential Manager => Windows Credentials=> 查找 git:https://github.com =>编辑=> 密码替换为你的 Github 个人 Access Taken => 完成

  • css 非阻塞的一种解决方案:当一个媒体查询的结果值计算出来是 false 的时候,浏览器仍然会下载样式表,但是不会在渲染页面之前等待样式表的资源可用。样式表一下载好,media 属性就必须被设置一个可用的值,以便样式规则能被应用到 html 文档中。onload 事件就可以用来将 media 属性切换到 all:

<!-- 方法1 -->
<link
  rel="stylesheet"
  href="css.css"
  media="none"
  onload="if(media!='all')media='all'"
/>
<!-- 方法2 -->
<link
  rel="preload"
  as="style"
  href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
/>
<script>
  {
    `
          let link = document.createElement('link')
          link.setAttribute('rel', 'stylesheet')
          link.setAttribute('href', 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap')
          document.head.appendChild(link)
        `;
  }
</script>
let startX: number;
let endX: number;

const onTouchStart = (e: any) => {
  startX = e.targetTouches[0].pageX;
};

const onTouchEnd = (e: any) => {
  endX = e.changedTouches[0].pageX;
  if (endX && endX && Math.abs(endX - startX) > 50) {
    if (endX - startX > 0) {
      // allowSlideNext allowSlidePrev 可以控制是否运行向对应方向滑动
      swiperInstance?.slidePrev();
    } else {
      swiperInstance?.slideNext();
    }
  }
};
// 滑动事件 Hooks
export const useTouchEvent = () => {
  // useRef 的内容发生变化时,它不会通知你
  // 更改`.current`属性不会导致重新渲染。因为他一直是一个引用。
  const startX = useRef(0);
  const swiperInstance = useRef<any>(null);

  const setSwiperInstance = useCallback((el: any) => {
    swiperInstance.current = el;
  }, []);
  const onTouchStart = useCallback((e: any) => {
    startX.current = e.targetTouches[0].pageX;
  }, []);
  const onTouchEnd = useCallback((e: any) => {
    // 执行滑动逻辑
    const endX = e.changedTouches[0].pageX;
    if (endX && endX && Math.abs(endX - startX.current) > 50) {
      if (endX - startX.current > 0) {
        swiperInstance?.current?.slidePrev();
      } else {
        swiperInstance?.current?.slideNext();
      }
    }
  }, []);

  return { onTouchStart, onTouchEnd, setSwiperInstance };
};
  • How to fetch data with React Hooks
  • React swiperjs 使用:
    • swiperjs
    • swiper 中文文档
    • onSwiper:接收 Swiper 实例的回调;
    • 在最外层的容器上增加className="swiper-no-swiping"可以禁止手动拖动滑动;
    • 宽度可以设置:width={(window.innerWidth || 360) / 2};【强制 Swiper 的宽度(px),当你的 Swiper 在隐藏状态下初始化时用得上。这个参数会使自适应失效。】
    • onClick 可以这么用:
      const onClick = (e: any) => {
      if (e.clickedIndex > activeIndex) {
        swip?.slideNext();
      } else if (e.clickedIndex < activeIndex) {
        swip?.slidePrev();
      }
      };
      
  • 点击时获取 DOM 上的值(属性):可以通过e.target.dataset.xxx获取元素上通过data-xxx设置的值。

  • PC、手机判断
const isMobile = () =>
  navigator.userAgent.match(
    /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
  ) !== null;
  • Less 函数
// global.less
@default-w: 360;
.px2vw(@px, @width: @default-w) {
  @var: (@px / @width) * 100;
  @vw: ~'@{var}vw';
}

// index.less
@import url('global.less');
.radioButton_wrap {
  display: flex;
  flex-direction: column;
  .radioButton {
    position: relative;
    label {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      pointer-events: none;
      color: #fff;
    }
    input[type='radio'] {
      // 去除radio小圆圈
      appearance: none;
      width: .px2vw(328) [ @vw];
      height: .px2vw(56) [ @vw];
      background: #000000;
      box-sizing: border-box;
      border: 1.5px solid #2e9df3;
      border-radius: .px2vw(28) [ @vw];
    }

    input[type='radio']:checked {
      background: #2e9df3;
    }

    input[type='radio']:checked {
      color: #fff;
    }
  }
}
  • 如何重置 Mac 的 SMC:重置系统管理控制器 (SMC) 可以解决某些与电源、电池、风扇和其他功能相关的问题。【如:电脑因出现问题而重新启动。请按一下按键,或等几秒钟以继续启动。】

  • 在使用 Form.Item 获取表单数据的时候,千万要注意,对于最外层的标签,同级别的只能有一个。如:下面的 InputNumber 值会取不到,因为Form.Item中还包含了“for each number”。

<Form.Item
  label='Enter amount'
  name='amount'
  rules={[
    {
      required: true,
      type: 'number',
      min: 1,
      max: 999,
      message: 'Please input amount!',
    },
  ]}
>
  <InputNumber
    precision={0}
    placeholder='Please input amount!'
    style=
  />{' '}
  for each number
</Form.Item>
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
// 渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()
  • Form 表单中,在黏贴(Paste)的时候对剪贴板内容进行格式化或者其他处理的方法:

    • onPaste:弊端为难以追加黏贴,如果要实现追加黏贴,需要读取光标位置、getSelection等,会很麻烦;
    onPaste: (e) => {
      // 阻止默认事件
      e.preventDefault();
      form.setFieldsValue({
        // 获取剪贴板内容并格式化(如处理掉多余的空格、替换换行符等),然后设置给表单
        phone_number: e.clipboardData.getData('text').replace(/\s+/g, ' '),
      });
    };
    
    • 结合 onPaste 和 onChange 实现:
    onPaste: () => {
      // 加锁,避免无意义的form.setFieldsValue
      IsOnPaste.current = true;
    },
    onChange: () => {
      // 只有是黏贴引起的Change才需要格式化
      if (IsOnPaste.current) {
        // 解锁
        IsOnPaste.current = false;
        form.setFieldsValue({
          // 获取表单的值,处理后再设置回去
          phone_number:form.getFieldValue('phone_number').replace(/\s+/g, ' ')
        })
      }
    }
    
  • less 插值:

    • 法一:
      .color(@token) {
      color: var(~'--color-@{token}');
      }
      body {
      background: .color('abc') [color];
      }
      
    • 法二:
      .color(@token) {
      @color: var(~'--color-@{token}');
      }
      body {
      .color('abcd'); //调用
      background: @color; // 使用返回值
      }
      

Exploration and Discovery

前端装逼技巧

CSS

惊蛰、春分、清明

2021.11.23 2022.01.23 2022.03.21

Knowledge and Experience

  • 防止越界的简写方式:
// 原写法
const nextValue = currentValue === 4 ? currentValue : currentValue + 1;
const preValue = currentValue === 0 ? currentValue : currentValue - 1;

// 新的写法
const nextValue = Math.min(currentValue + 1, 4);
const preValue = Math.max(currentValue - 1, 0);
  • 在 flex 布局中,有时候会遇到,在缩小容器时,最左边的元素被挤压/ 压扁了,这时只需要给它添加一个 css 样式:flex-shrink:0;;理论参考:深入理解 flex 布局的 flex-grow、flex-shrink、flex-basis

    flex 有三个属性值,分别是 flex-growflex-shrinkflex-basis,默认值是 0 1 autoFlex 布局教程:语法篇

    • flex-basis:用于设置子项的占用空间;如果没设置或者为 auto;
    • flex-grow: 用来“瓜分”父项的“剩余空间”(拉伸);未设置默认为 0;
    • flex-shrink:用来“吸收”超出的空间(压缩);未设置默认为 1;
  • 统计 excel 类表格中某列文字出现频率/频次并筛选:

const name2Obj = `若川
战场小包
陈_杨
cv竹叶
獨釣寒江雪
BraveWang
蓝色夜晚
西瓜watermelon
海的对岸
政采云前端团队
是洋柿子啊
前端小智
獨釣寒江雪
由也_`
  .split('\n')
  .reduce((pre, next) => {
    return { ...pre, [next]: pre[next] ? pre[next] + 1 : 1 };
  }, {});
// 按照频率倒序
Object.entries(name2Obj).sort((a, b) => b[1] - a[1]);
// 筛选出现大于1次的
Object.entries(name2Obj).filter((item) => item[1] > 1);
.tick {
  width: 8.5px;
  height: 16.5px;
  border-color: #00adffff;
  border-style: solid;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
const count = 3;
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat,picture&noinfo`;
fetch(fakeDataUrl)
  .then((res) => res.json())
  .then((res) => console.log(res));
  • Mac 上录制视频快捷键:command ➕ shift ➕ 5;

  • 表格吸顶的实现:

// 实现一
<Table
  sticky
  className={styles.table}
  columns={columns}
  dataSource={data}
  pagination=
/>
// 实现二
<>
  <Table
    sticky
    columns={columns as any}
    dataSource={dataSource}
    pagination={false}
    loading={loading}
    onChange={pageChange}
    scroll=
  />
  <footer className={styles.tableFooter}>
    <Pagination
      showSizeChanger
      showQuickJumper
      current={current}
      pageSize={pageSize}
      total={total}
      onChange={(current, size) => {
        setPageSize(size as number);
        setCurrent(pageSize === size ? current : 1);
      }}
    />
  </footer>
</>
// 实现一
.table {
  :global {
    .ant-pagination {
      position: sticky;
      bottom: 0;
      padding: 16px;
      background-color: #fff;
      width: 100%;
      text-align: center;
      z-index: 2;
      border-radius: 5px;
    }
  }
}
// 实现二
.tableFooter {
  position: sticky;
  bottom: 0;
  padding: 16px;
  background-color: #fff;
  width: 100%;
  text-align: center;
  z-index: 2;
  border-radius: 5px;
}

Exploration and Discovery

浏览器插件

WebView 相关

前端换肤

JavaScript Visualized Series’ Articles

React Hooks

CSS 开发者大会

Search

    欢迎与我交流

    江南逰子

    Table of Contents