Hello World - 学习笔记
type: channel
link: https://blog.xunbaidu.com/category/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
description: 编程类的学习笔记？
items: 几种修改前端第三方库（bug）的方法
items: 前端性能指标学习
items: 第3章-优先级和层叠
items: 第2章-选择符
items: 第5章-字体
items: JS中this的指向问题
items: H5项目遇到的兼容问题汇总
items: html2canvas有关问题总结
items: 为什么Payload会不显示？
items: Swiper踩坑经历

几种修改前端第三方库（bug）的方法
type: item
date: 2024-11-18T00:08:47+08:00
description: 1.在github上提issue通常第三方库都有自己的github仓库地址或者官网，如果有github仓库的话就可以在github上提issue，要注意的是一定要按照仓库作者规定的格式提issue，写明bug复现过程，否则issue可能会被忽略。这种方法理论上是最好的，但是也有一些缺点：仓库作者可能不维护了，那么issue将不会被解决仓库作者不认同观点，可能会拒绝所提的issue仓库作者接受所提的issue，但认为其优先级低，可能需要很长时间才会解决2.fork代码自己修改如果急需解决bug的话，可以尝试自己把原仓库fork下来进行修改，并且将代码发布到npm仓库中，供项目使用。这种方法的缺点有：增加维护成本，需要和原仓库保持同步，否则会丢失更新后期可能会和原仓库出现难以合并的分歧注意合法性问题，可能需要原作者授权综上，如果原仓库一直在更新的话，需要考虑原仓库和自己仓库的代码同步问题，因此这种方法更适合在原仓库不再维护的情况下使用。此外还可以给仓库作者提PR，这样就不用担心后续维护的问题了，也是一种比较好的方式，即满足了自己的需求还为开源库做了贡献，但前提是作者接受了PR。3.使用pack-package打补丁在不修改源代码的情况下，也可以通过直接修改其打包后的代码来实现所需功能，步骤如下：安装pack-packagenpm i patch-package修改代码在node_modules中找到对应模块的位置，以element-plus为例，假设要对Button组件的点击事件进行修改，找到Button组件以及代码中相应位置，如图所示，添加代码console.log('click')。代码修改后的预期结果是所有用了Button组件的地方，在点击按钮时都会在控制台输出click。验证代码模版中使用el-button组件: <el-button>Default</el-button> 如下图所示代码中并没有主动监听el-button的click事件，但是每次点击时都会在控制台输出"click"注：如果使用了vite，要注意在重新运行前清除缓存，否则可能会出现代码不生效的问题。保存修改的代码为了便于代码的分发，需要把在element-ui中修改的代码保存下来，通过执行命令npx patch-package package-name会生成一个.patch文件，里面包含了修改前后的diff信息。其中内容为：diff --git a/node_modules/element-plus/es/components/button/src/use-button.mjs b/node_modules/element-plus/es/components/button/src/use-button.mjs index 884214f..59c9c6c 100644 --- a/node_modules/element-plus/es/components/button/src/use-button.mjs +++ b/node_modules/element-plus/es/components/button/src/use-button.mjs @@ -52,6 +52,7 @@ const useButton = (props, emit) => { return false; }); const handleClick = (evt) => { + console.log('click') if (_disabled.value || props.loading) { evt.stopPropagation(); return;至此，只要执行patch-package命令即可将patch中的代码并入到node_modules对应文件中。为简化此过程，通常在package.json的scripts对象中加入"postinstall": "patch-package"配置，这样每当执行npm install之后，都会自动执行patch-package命令。适用场景和第二种方法的适用场景类似，但更加适用于修改一些较小的bug，因为node_modules中通常是打包后的代码，特别是经过混淆和压缩的代码，修改难度相对较高。另外打补丁的方法也不需要去另外维护仓库。需要注意的是用这种方法修改，需要锁定包的版本，如果后期需要升级包的话，补丁也需要看实际情况去做相应修改。4.pnpm patch打补丁和pack-package类似。使用命令pnpm patch package-name，还是以element plus为例，命令为pnpm patch element-plus，执行命令后会将对应npm包复制到一个叫.pnpm_patches的目录中，如下图所示。…

前端性能指标学习
type: item
date: 2024-06-23T19:27:00+08:00
description: web核心性能指标（Core Web Vitals）Web Vitals是谷歌开发的一系列指标，用于从不同方面去衡量网页的性能。而Core Web Vitals是Web Vitals的一个子集，包含了3个需要特别关注的指标，它们在谷歌搜索算法中扮演着一定的角色，并能够改善网站在谷歌搜索中的权重。LCP（Largest Contentful Paint，最大内容绘制）概念LCP用于用于测量用户感知到的页面加载速度，指从页面开始加载到页面中最大的内容元素被渲染到屏幕上的时间点。这个最大的内容元素可能是图片、视频、SVG、含有背景图的元素、包含文本节点或其它含有文本子元素的块级元素。需要注意的是，随着页面的不断加载，页面中的最大内容元素可能会发生变化，因为更大的内容元素可能还未加载，因此LCP在页面加载过程中也会不断更新直至页面加载完毕。LCP包含从上一个网页开始的所有卸载时间、连接设置时间、重定向时间和首字节时间（TTFB）。测量方式通过PerformanceObserver监测LCP：new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { console.log('LCP candidate:', entry.startTime, entry); } }).observe({type: 'largest-contentful-paint', buffered: true});根据LCP的定义，随着网页的加载以上代码会获取到多个startTime值，而所获取到的最后一个值便是页面的LCP值。通过web-vitals监测LCP：import {onLCP} from 'web-vitals'; // Measure and log LCP as soon as it's available. onLCP(console.log);为获取更精确的LCP，推荐使用第二种方式，详见Web.dev。指标分数为提供良好的用户体验，网站应努力将Largest Contentful Paint控制在2.5s以内。为确保大多数用户都达到此目标，最好衡量一下网页加载的第 75 个百分位（按移动设备和桌面设备细分）。INP（Interaction to Next Paint，下一次绘制的交互）概念INP衡量页面在整个生命周期中的交互能力（响应能力），通过观察用户在访问网页期间发生的所有点击、点按和键盘互动的延迟时间，评估网页对用户互动的总体响应情况。INP的计算方法是观察用户与网页进行的所有交互并记录延迟时间，最终INP值是观测到的最长交互时间，离群值会被忽略。对于大多数网站来说，延迟时间最长的交互就是INP，但对于包含大量交互的页面来说可能会不准确，因此INP在计算过程中会忽略达到50次交互中的延迟最长的一次交互。根据INP的定义，只会监测以下类型的交互：使用鼠标点击。点按带有触摸屏的设备。按实体键盘或屏幕键盘上的某个键。如果页面一直未接收到以上类型的交互，那么页面将不会返回INP值。2024年3月，INP取代了FID成为Core Web Vitals。测量方式通过web-vitals监测INP：import {onINP} from 'web-vitals'; onINP(({name, value, rating}) => { console.log(name); // 'INP' console.log(value); // 512 console.log(rating); // 'poor' });指标分数为提供良好的用户体验，网站应努力将Largest Contentful Paint控制在200ms以内，为确保大多数用户都达到此目标，最好衡量一下网页加载实际记录的第 75 个百分位（按移动设备和桌面设备细分）：CLS（Cumulative Layout Shift，累计布局偏移）概念布局偏移指页面中的元素所发生的意外移动，这主要发生在当资源以异步方式加载或DOM元素被动态添加到页面的现有内容中时。比如页面上方有一个广告，当用户刚进入页面时还未获取到广告数据，在获取到广告数据后，广告横幅被加载到页面顶部，此时页面中其它的内容都向下移动了广告横幅的高度，这就叫布局偏移。CLS 用于衡量再页面的整个生命周期中发生的所有意外布局偏移的得分之和。布局的移动可能发生在可见元素从一帧到下一帧改变的任何时候，并且仅当现有元素更改其起始位置时才会发生布局偏移。如果将新元素添加到DOM或现有元素更改了大小，只要没有导致其他可见元素更改其起始位置，就不应将其视为布局偏移。测量方式布局偏移计算方式：new PerformanceObserver(…

第3章-优先级和层叠
type: item
date: 2024-06-02T23:44:49+08:00
description: 1.继承和层叠继承（Inheritance）是从一个元素向其后代元素传递属性值所采用的机制。确定应向一个元素应用哪些值时，用户代理不仅要考虑继承，还要考虑声明的特殊性（优先级），以及声明本身的来源，这个过程就叫做层叠（cascade）。2.CSS特殊性值的计算规则对于选择器中给定的各个ID属性值，加 0, 1, 0, 0对于选择器中给的的各个类属性值、属性选择或伪类，加 0, 0, 1, 0对于选择器中给定的各个元素和伪元素，加 0, 0, 0, 1。内联样式的特殊性为 1, 0, 0, 0结合符和通配选择器对特殊性没有任何贡献，但又不完全一样。结合符和继承根本没有特殊性，通配选择器的特殊性是0, 0, 0, 0，是要高于结合符和继承的。!important 令该样式规则获取到最高的优先级，甚至是超过内联样式h1 {color: red;} /* specificity = 0, 0, 0, 1 */ p, em {color: red;} /* specificity = 0, 0, 0, 2 */ .grape {color: red;} /* specificity = 0, 0, 1, 0 */ *.grape {color: red;} /* specificity = 0, 0, 1, 0 */ p.bright em.dark {color: red;} /* specificity = 0, 0, 2, 2 */ #id666 {color: red;} /* specificity = 0, 1, 0, 0 */ div#sidebar *[href] {color: red;} /* specificity = 0, 1, 1, 1 */注意：通配选择器的特殊性是0, 0, 0, 0，这与根本没有特殊性是有区别的，后续会讲到。相对来说，结合符则根本没有特殊性，甚至连0特殊性都没有。声明性与特殊性一旦确定一个选择器的特殊性，这个值将被授予其所有的相关声明。如h1, h2.section {color: red; background: black;}，为了计算优先级，用户代理会将完整的规则"解组"为单独的规则，如下所示，这样计算出的优先级就可以赋值给每一个相关声明。h1 {color: red;} /* 0, 0, 0, 1 */ h1 {background: black;} /* 0, 0, 0, 1 */ h2.section {color: red;} /* 0, 0, 1, 1 */ h2.section {background: black;} /* 0, 0, 1, 1 */任何情况下，用户代理都会确定那些规则与一个元素匹配，计算出所有相关的声明及其特殊性，确定哪些规则胜出，然后将胜出的规则应用到元素，从而得到应用样式后的结果。每个元素、选择器和声明上都必须完成这些工作。这个行为是层叠的一个重要部分。重要性（重要声明important declaration）在声明的结束分号之前插入!important来标志重要声明。重要声明并没有特殊的特殊性值。如果一个重要声明和一个非重要声明冲突，胜出的总是重要声明。另外：重要声明与非重要声明的特殊性是分开处理的。也就是说，所有重要声明会放在一起，它们之间的冲突在这个范围内解决。同样，非重要的声明作为一个整体，其中的冲突使用特殊性解决。3.继承与特殊性基于继承，样式不仅可以应用到指定的元素，还可以应用到它的后代元素。但是要注意继承到后代的样式是没有特殊性的，连0都没有。所以: Title 今天是周五 如上所示的代码，我们知道通配选择器的特殊性是0，然而继承根本没有特殊性，所以上面的代码运行结果是”周五“是灰色而不是红色。总之，0特殊性比无特殊性要强。4.层叠CSS2.1的层叠规则找出匹配特定元素的所有规则按显示权重排序应用到特定元素上的所有声明。标志!important的规则的权重要高于没有!important标志的规则。按来源对应用到给定元素的所有声明排序。共有3种来源：创作人员、读者和用户代理。正常情况下，创作人员的样式要胜过读者的样式。有!important标志的读者样式要强于所有其他样式，这包括有!important标志的创作人员样式。创作人员样式和读者样式都比用户代理的默认样式要强。按照特殊性对应用到给定元素的所有声明排序。有较高特殊性的元素权重要大于有较低特殊性的元素。按出现顺序对应用到给定元素的所有声明排序。一个声明在样式表或文档中越后出现，他的权重就越大。如果样式表中有导入的样式表，一般认为出现在导入样式表中的声明在前，主样式表中的所有声明在在后。声明的权重级别在声明权重方面要考虑从5级，权重由大到小的顺序依次为：读者的重要声明创作人员的重要…

第2章-选择符
type: item
date: 2024-05-21T23:43:00+08:00
description: 1.样式的基本规则元素选择器元素选择器（element selector）通常是HTML元素。声明和关键字声明的格式统一为属性: 值;的格式，一个声明块中可能有一个或多个声明，要注意的是如果声明中的属性或值有误，整个规则都将会被忽略（书上是这样写的，但实际上应该是只会忽略有问题的声明）。属性的值可以是多个关键字，关键字之间通常以空格分隔，有时也会用斜线或逗号。2.分组选择器和选择器分组如h2, p {color: gray;}这样的一条css规则，将会分别作用于h2和p元素上，即应用于这两个选择器所引用的元素。这个逗号告诉浏览器，规则包含两个不同的选择器。可以将任意多个选择器分组在一起。通配选择器* {color: white;}*号可以视为是一个通配符，这个选择器可以于任何元素匹配。使用这个选择器可以把文档中所有元素的color值都指定为white。声明分组如: h2, p {color: gray; font: 18px;}（大括号内的是声明块，每个逗号与之前的一部分叫做声明）注意: 对声明分组的时候，一定要在各个声明的最后使用分号。2.类选择器和ID选择器类选择器和ID选择器允许以一种独立于文档元素的方式来指定样式，这些选择器可以单独使用，也可以与其他元素选择器结合使用。类选择器要应用样式而不考虑具体涉及的元素，最常用的方法就是使用类选择器。如p.waning {font-weight: bold;}，选择器将会匹配class属性包含warning的所有p元素。如.waning {font-weight: bold;}，选择器将会匹配class属性包含warning的所有元素。注意：使用id选择器、类选择器、属性选择器、伪类、伪元素选择器时，如果没有使用元素选择器，那么将隐式蕴含通用选择器。多类选择器通过把两个类选择器相连在一起，可以选择同时包含这些类名的元素（类名的顺序不限）。如.warning.urgent这样的选择器。如果一个多类选择器包含类名列表（类名以空格分隔）中所没有的一个类名，匹配就会失败。如p.warning.help {background: red;}。这个选择器将值匹配class包含warning和help的那些p元素。So，如果一个p元素的class属性中只有词warning和urgent，将不能被匹配到。不过，他能匹配以下元素: Help me!!! id选择器ID选择器引用元素id属性中的值。其前面有一个‘#’号区别于类选择器的'.'。，因为ID属性不允许有以空格分隔得词列表。 类选择器还是id选择器可以为多个元素指定类，但是ID选择器只会匹配到一个元素（按照规则）。3.属性选择器分类：简单属性选择器、精准属性值选择器、部分匹配属性值选择器简单属性选择器（根据是否有某属性选择）如h1[title] {color: red;}将会选择所有具有title属性的h1元素，并且不会考虑title属性的值是什么。a[href][title] {font-weight: bold;}会将同时拥有href和title属性的HTML超链接元素设置为粗体精准属性值选择器（根据具体属性值选择）如h1[title="my"] {color: red;}将会选择所有具有title属性且值为"my"的h1元素。如h1[title="my" href="/test"] {color: red;}将会选择所有同时具有title属性且值为“my”以及href属性且值为“/test”的元素。部分匹配属性值选择器（根据部分属性值选择）如果属性能接受词列表（词之间用空格跟个），可以根据其中的任意一个词进行选择，如class属性。p[class~="warning"] {color: red;}，注意选择器中的'~'波浪号，这将导致选择器根据元素属性值中出现的一个用空格分隔的词来完成选择。类型描述[foo\|="bar"]选择foo属性值以bar和一个英文破折号（U+002D）开头，或者值就是bar本身的所有元素（起始值属性选择器）[foo~="bar"]选择foo属性值是包含bar这个词的一组词的所有元素[foo^="bar"]选择foo属性值以"bar"开头的所有元素[foo$="bar"]选择foo属性值以"bar"结尾的所有元素[foo*="bar"]选择foo属性值中包含子串"bar"的所有元素不区分大小写标识符如a[href$=".pdf" i]，CSS Selectors Level 4指明，在属性选择器结束方括号前加上i，将在匹配属性值时不区分大小写（注意是属性值而不是属性名）。4.根据文档结构选择后代选择器h1 em {color: red;}这个规则会把作为h1元素后代（不局限于子元素） 的em元素的文本变为re…

第5章-字体
type: item
date: 2024-04-21T22:36:00+08:00
description: 1.字体族介绍font-family（字体族/字体系列）指具有同种类似特性的一系列字体的组合或者说是同种变形的组合，并不单单指的是一个字体，包含了一个或多个的font-face（字型）。 为了覆盖所有情况，CSS定义了5种通用字体族：Serif字体（衬线字体）字形宽度各异，且有上下短线。包括Times、Georgia、New Centure Schoolbook。Sans-serif字体（无衬线字体）字形宽度各异，但没有上下短线。包括Helvetica、Geneva、Verdana、Arial、Univers。Monospace字体（等宽字体）字形宽度相等，每个字符在横向上所占的空间是一样的，可能有也可能没有上下短线。这种字体通常用于显示编程代码或表格数据。包括Courier、Courier New、Andale Mono。Cursive字体（手写体、草书字体），这种字体在笔划末端有较大的花饰，且比衬线字体华丽。包括Zapf Chancery、Author、Comic Sans。Fantasy字体（奇幻字体、艺术体）难以用任何特征来定义，但可以确定无法将其划归为其它类。包括Western、Woodblock、Klingon。使用通用字体族如果希望使用一种Sans-serif字体，而且不关心是哪一种具体字体，那么可以使用声明body {font-family: sans-serif;}。这样用户代理就会从sans-serif字体族中选择一个字体（如Helvetica)，并将其应用到body元素。因为有继承，还会将其应用到body元素中包含的所有元素，除非优先级更高的选择符覆盖了这个属性。指定字体系列如这样的声明h1 {font-family: Georgia;}，如果用户代理上Georgia字体不可用的话， 那么用户代理将不能使用这个规则，而是使用用户代理的默认字体来进行显示。此时应该指定h1 {font-family: Georgia serif;}，这样的话如果用户代理上没有Georgia字体，那么用户代理将会选择serif字体进行使用。 此外font-family还可以指定多个字体，用户代理将会按照顺序取匹配，最先匹配到的字体将会被使用。引号的使用如果一个字体出现了空格或者一些特殊符号如#、$**等，那么这个字体需要加上引号（根据实际情况可选单引号或者双引号）才能够Working Normally。 另外注意使用通用字体系列名（如serif）是不可以加引号的，加引号的话，用户代理会将其视为与此同名的特定字体而不是一个通用字体系列。也就是说与font-family中可用的关键字同名的字体名称必须放在引号里，这样才能与关键字区分开。2.@font-face作用是可在设计中使用自定义的字体。@fant-face { font-family: "name"; src: url("name.otf"), url("fonts/font.otf") format("opentype"), local("Helvetica"); }font-family和src是两个必填描述符。 @font-face中描述符font-family与font-family属性中出现的字体族名是一样的。 src描述符以逗号分隔的列表用于提供后备字体文件，format用于为不带有常规扩展名的字体文件指定格式，以防用户代理不识别，local指定本地已安装的字体（可用于为本地安装的字体自定义名称，比如添加别名或者简称）。注意src的url适用同源策略，如果指向其它服务器，通常无法下载，除非服务器设置允许跨域访问。支持的字体格式值格式embedded-opentypeEOT（Embedded OpenType）opentypeOTF（OopenTYpe）svgSVG（Scalable Vector Graphic）truetypeTTF（TrueType）woffWOFF（Web Open Font Format）其它字体描述符描述符默认值说明font-stylenormal区分常规、斜体和倾斜字型font-weightnormal区分不同的自重（粗细）font-stretchnormal区分不同的字符宽度font-variantnormal区分众多字体变体（例如小号大写字母），在很多方面与CSS中的font-feature-setting很像font-feature-settingsnormal直接访问OpenType的底层特性（例如启用连字，如将“>=”自动替换显示成“≥”）unicode-rangeU+0-10FFFF定义或指定字体中可用的字符范围，或者指定只有某些字才应用字体3.字体加粗（font-weight）font-weight属性可以使用字体系列的…

JS中this的指向问题
type: item
date: 2024-03-01T00:14:59+08:00
description: 最近碰到了一个问题，环境是vue2、ts、vue-class-component，写一个方法时发现声明的变量虽可以访问到但没有响应式，随后发现方法中的this不是VueComponent，在检查为什么this指向有问题时发现新写的方法用的是箭头函数，改成普通函数后就正常了，对于这块有点疑惑，因此整理记录关于this指向的情况。1.不同情况下this的指向方法/函数调用中的thiswindow.name = 'win' function fun () { console.log(this.name) } let obj1 = { name: 'jack', fun } let obj2 = { name: 'bob', } obj2.fun = obj1.fun fun() // win obj1.fun() // jack obj2.fun() // bob如上代码，直接执行fun函数，打印“win”，表明此时函数中的this指向的对象是window。而使用obj1.fun()调用fun函数，打印“jack”，此时this指向的对象是obj1。obj2.fun()的执行结果为“bob”，表明obj2对象的fun函数虽然是由obj1的fun函数赋值得到，但由于是obj2对象调用的，所以this指向obj2，打印了obj2的name属性。window.name = 'win' let fun = () => { console.log(this.name) } let obj = { name: 'jack', funA } obj.fun() // win如上代码，当fun函数为箭头函数时，因为定义funB时所在的作用域是在全局作用域window下，所以funB内的this是window，因此obj.fun()的打印结果是”win“而非”jack“。事件回调中的this行内绑定 直接将函数执行语句"handleClick()"作为属性绑定到dom标签上，当click事件触发时，语句可视为是在全局作用域下执行的，因此this指向window。 当回调函数是箭头函数时，函数内this也为window。js动态绑定 通过js赋值函数给dom对象的onclick属性的方式绑定回调函数，回调函数是被dom对象所调用的，因此this指向dom对象。 当绑定的回调函数是箭头函数时，函数内this指向window。事件监听 通过事件监听方法绑定的回调函数，this同样指向了dom对象。 当绑定的回调函数是箭头函数时，函数内this指向window。构造函数中的thisfunction Obj() { console.log(this) } let o = new Obj() // Obj {} console.log(this) // Window {}如上代码第五行执行new Obj()语句时，打印了“Obj {}”，这是因为在new关键字会首先创建一个空对象，并将控对象的隐式原型指向构造函数的显式原型，最后将构造函数中的this指向了这个新创建的对象，因此在构造函数中，this会指向一个新创建的对象，同时它也是new语句的返回值。另外箭头函数由于没有自己的this，是不可以作为构造函数的。call、apply、bindcall、apply和bind是js中三个很神奇的函数，使用它们可以改变函数体内部this的指向。let func = function() { console.log(this.name, [...arguments].join(',')) } let obj1 = { name: 'obj1', func } let obj2 = { name: 'obj2', } obj1.func(0) // obj1 0 obj1.func.call(obj2, 1, 2, 3) // obj2 1,2,3 obj1.func.apply(obj2, [4, 5, 6]) // obj2 4,5,6如上代码，call和apply方法都可以改变函数内部this的指向，区别在于call方法第一个参数是所要改变的this（上下文），其它参数是func函数的参数，apply方法的第二个参数是fun函数参数的数组。let func = function() { console.log(this.name, [...arguments].join(',')) } let obj1 = { name: 'obj1', func } let obj2 = { name: 'obj2', } let newFunc = obj1.func.bind(obj2) newFunc(1, 2, 3) // obj2 1,2,3bind方法可以返回一个新的函…

H5项目遇到的兼容问题汇总
type: item
date: 2024-01-28T21:24:54+08:00
description: 项目包括一个loading组件以及Swiper控制的内容组件，Swiper的每个Slide也是一个组件，通过Swiper滑动控制切换不同的显示组件，DOM结构如下所示： <Loading v-show="showLoading" class="loading" :loadingText="loadingText"></Loading> <swiper v-show="!showLoading"> <swiper-slide v-for="(item, index) in componentQueue"> <component ref="components" :is="item" direction="vertical" /> </swiper-slide> </swiper> Swiper不能滑动对于IOS12及以下系统，Swiper11版本无法通过触摸手势触发滑动操作，调试发现直接调用slideNext和slidePre方法是可以切换下一项和上一项的。由于手动监听DOM上的touchstart、和touchmove事件是可以正常触发的，因此解决方法是通过监听touchstart、和touchmove事件根据手指移动情况判断上滑以及下滑操作，并分别调用slideNext和slidePre方法实现滑动切换。Swiper不能正常显示在项目开发时刚开始使用的是v-if控制loading页和内容页容器的显隐，但这样内容页DOM结构并不会生成，loading过程中内容页的图片资源也不会提前加载，loading页无法完成“预加载”资源的功能。更换为v-show之后虽然没有这个问题，但由于DOM没正常显示引起Swiper没有正常初始化，从而导致轮博图出现不能正常显示的问题。Swiper提供了update方法，官方文档称“应该在手动添加/删除幻灯片、隐藏/显示幻灯片或使用Swiper进行任何自定义DOM修改后调用该方法”。因此通过在初始化后添加了一个定时器调用update方法解决。然而在IOS12及以下版本的手机上这个问题又复现了，推测是定时器的延迟时间不合适导致的。最终解决方案是设置observer属性为true，这样Swiper将会自己启动检测，每当显示/隐藏、添加/删除子元素时都会自动进行更新。IOS12打开页面报错Unexpected token '.'问题发现在IOS12及以下版本。这个错误是在loading页加载时抛出的，根据log信息并不能直接定位到具体报错代码行列，只能定位至问题的组件。因此只能逐行代码进行查错，先把 和 注释掉，再逐步放开里面的代码，最终发现是使用可选链运算符?的两行代码导致的报错。因此确定是项目使用的vue3+vite的技术栈不兼容导致的，未对可选链?运算符进行转换，低版本浏览器不能识别而报错。解决方法：使用其他写法替换可选链?运算符安装模块@babel/plugin-proposal-optional-chaining，并配置babel.config.tsmodule.exports = { presets: ['@vue/app'], plugins: ["@babel/plugin-proposal-optional-chaining"] }html2canvas转图片触发DOM动画项目中有一个页面进入时有约3s的入场动画，同时此页面还有保存图片的需求。保存图片使用的是html2canvas插件，这样就产生了一个问题，html2canvas在绘制DOM时会重新触发页面的入场动画，最终截取到的图片是动画过程中的一帧，并不是最终的样式，因此需要在保存图片时禁止动画播放，并且在关闭保存动画组件后还不能触发及影响页面动画。前期采用的方案是临时加了一个css类：animation-duration: 0.001s !important; animation-delay: 0.001s !important;设置动画的过渡时间和延迟时间为0.001s，目的是能够快速执行动画。然而在IOS12及以下手机上这两个属性并不能取得预期效果。最终方法是使用以下属性，直接取消动画：opacity: 1 !important; // DOM元素默认opacity是0，使delay动画执行前隐藏元素 animation: none;图片旋转动画不显示需求是需要在点击图片时，图片会开始旋转，再次点击，图片停止旋转。原本的方法是通过设置animation-play-state属性来控制动画的暂停和播放，并且能够保留暂停时动画的状态。.music-play { animation-play-state: running; -webkit-animation-play-state: running; } .music-pause { …

html2canvas有关问题总结
type: item
date: 2024-01-07T21:15:00+08:00
description: 过去几个月中有两个需求都用到了html2canvas插件，目的是使Web中的DOM结构转为图片，但由于html2canvas的原理限制，遇到了一系列问题：跨域问题html2canvas转图片的原理是将传入的dom元素渲染到canvas画布上，然后利用canvas的toDataURL方式转位图片的。然而html2canvas在将dom元素绘制在canvas上时，如下html2canvas加载图片的核心代码所示：return await new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; //ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous if (isInlineBase64Image(src) || useCORS) { img.crossOrigin = 'anonymous'; } img.src = src; if (img.complete === true) { // Inline XML images may fail to parse, throwing an Error later on setTimeout(() => resolve(img), 500); } if (this._options.imageTimeout > 0) { setTimeout( () => reject(`Timed out (${this._options.imageTimeout}ms) loading image`), this._options.imageTimeout ); } });对于img会重新发起网络请求获取图片，因为是js发起的请求，所以对于有跨域限制的图片会提示跨域错误，而且可以注意到当传入的options参数设置useCORS时，是会设置img.crossOrigin = 'anonymous'的。要想解决这个问题，最简单的方法就是在图片服务器上进行设置允许跨域，但是这种方式在服务器自己不可控的情况下是行不通的。所以在解决问题的过程中就想着是否有前端侧的方法来避免这个问题呢？网上有很多文章介绍说可以给图片设置crossorigin="anonymous"、html2canvas的option设置userCORS: true、提前把跨域的网络图片转为base64等方法来解决，但是经过一系列折腾后发现，这些方法都是需要服务端的支持才行得通的。可行的方法主要包括：服务端配置跨域访问设置代理服务器图片数量固定且不多的情况下可选择放置在项目中图片显示不完整问题当dom元素有滚动条时在生成图片后可能会出现图片不完整，部分区域是空白的情况，可以调用图下代码 dom.scrollTo(0, dom.scrollHeight)控制滚动条至页面底部解决。另外还可能出现图片如果有偏移的话，可添加配置scrollY:0, scrollX:0,解决。所生成图片边缘有缝隙/空白问题由于不同设备的屏幕像素密度不一样，html2canvas在计算宽高的时候可能出现了误差导致。解决方法：设置宽度或高度减去几个像素，刚好把空白的地方裁掉（当然前提是在不影响图片内容的情况下）如果是纯色背景，可以设置html2cavas配置里的背景色为相同颜色即可透明图片异常显示的问题很奇怪的异常，最开始这个图片是以背景图方式显示的，最后换成img标签形式展示就没这个问题了。由dom元素的动画引起的显示不完整问题问题的根因是html2canvas在解析传入的dom节点之前会先去克隆一个副本，这个操作的过程可能就重新触发了动画的播放条件，于是html2cavas在将dom绘制至canvas时可能只是截取到了动画过程中的一个状态。解决这个问题的本质是要在html2cavans绘制dom时，dom的动画是不执行的或者动画已经执行完毕，具体解决方法包括：另外创建一个不可视且无动画版本的dom作为html2canvas的入参利用定时器保证在动画播放完毕时再执行html2canvas声明一个css类，设置规则animation-duration: 0s !important; animation-delay: 0s !important;使动画执行时间可延迟时间都为0s，这样dom会直接处于动画执行完毕的状态，当执行html2canvas之前动态添加这个类，当执行完毕后再删除掉这个类，不影响后续动画的执行。

为什么Payload会不显示？
type: item
date: 2023-12-17T18:18:28+08:00
description: 问题背景在一次使用Chrome开发者工具调试中，发现找不到接口参数了，但仔细检查代码后以及结合接口响应数据来看确确实实是传参数了的，并且后端也正常接收到了请求数据，当时没有深入探究原因，现在对这个问题进行一下复盘。接口数据传输的方式Payload和Form Data是接口数据传输的两种方式，浏览器通过Content-Type来进行区分使用哪种方式：当值为application/x-www-form-urlencoded或multipart/form-data时，是Form Data方式当值为application/json时，是Request Payload方式Request Payload如下代码所示，使用axios发起post请求，设置请求头'Content-Type':'application/json'，成功发起请求后可在调试工具Payload Tab面板中看到请求参数。Request Payload通常用于发送JSON 或 XML格式的数据。axios.post(host + '/payload', { username: 'admin', password: 'admin', }, { headers: { 'Content-Type': 'application/json' } }).then((res) => { console.log(res) }).catch((err) => { console.log(err) })Form Data如下代码所示，设置请求头'Content-Type':'application/x-www-form-urlencoded'，请求参数使用Qs插件进行序列化后使用axios发送，在调试工具Payload Tab面板中可看到请求参数为Form Data类型。Form Data类型通常用于发送表单数据，并且可用来进行上传文件。axios.post(host + '/payload', Qs.stringify({ username: 'admin', password: 'admin', }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then((res) => { console.log(res) }).catch((err) => { console.log(err) })如上图所示，再点击view source后，可看到实际上传输的数据是一个字符串，由于设置了正确的Content-Type，可以被浏览器正常识别。但如果请求参数不进行序列化，还是用Form Data方式进行请求，浏览器依然是能够正常发送出请求的，并且浏览器的Payload将会显示：但是如果Server端如果仍按照Form Data方式对请求参数处理，那么可能会出错。所以，请求参数一定要和Content-Type是对应的，否则可能会影响Server端对请求参数的正常解析。Query String Parameters另外在Chrome中，Query参数也会被放到Payload中，如下图所示：注：Chrome 96版本前，Request Payload和Form Data信息是在HeadersTab中的，96版本之后它们都被放到了PayloadTab中。问题复现所发生问题的接口参数是Request Payload格式的，并且注意到其中有个对象数据量是很大的，因此根据这一特征构造一个请求尝试复现问题：如图所示，通过循环构造了一个长字符串作为对象data属性值，经过调整不同的循环次数进行测试，确定了当Content-Length超过10000000字节时Payload Tab就会消失，并且Preview和Response显示“Failed to load response data: request content was evicted from inspector cache”。这句话翻译为中文意思是“未能加载响应数据：请求内容已从检查器缓存中收回”，大概意思就是由于超出缓存限制，请求参数以及响应参数都不能正常显示了。通过观察后端接口的请求以及浏览器请求状态码（200 OK）可知，虽然调试工具不显示请求以及响应数据，但实际上是没有影响接口的正常请求以及响应的，并且前端也能够正常接收到后端响应数据，也就是说这个问题是不影响项目的正常运行的。另外通过测试发现，当Server端的响应数据过长时，在浏览器调试工具中也会发生同样的事情，而且也不会实际影响接口数据的响应。问题原因通过查询发现以下一些情况都可能导致此问题：请求的请求数据超过了浏览器的缓存限制请求的响应数据超过了浏览器的缓存限制在浏览器的开发者工具中查看了大量的…

Swiper踩坑经历
type: item
date: 2023-12-03T23:23:00+08:00
description: 背景有个移动端项目用了Vant组件库进行开发，其中有个需求要实现一个轮播图的效果，但是Vant组件库自带的轮播图组件功能有限，只能实现一些很基础的效果，而且在其基础上只进行一些样式的调整也难以实现想要的效果，所以就考虑使用其它的插件。Swiper是很著名的一个用于轮播图及移动动画的插件，面向手机、平板电脑等移动终端，并且开源免费。在网上进行搜索之后发现大多数的用法都是用原生js方式所实现的，并且Swiper官方在7版本之前也是没有适配Vue的，因此为了在Vue中能以组件方式进行调用Swiper，还诞生了一个12.7k star的第三方开源项目vue-awesome-swiper。另外在Vant的官网上也提到，如果需要更复杂的轮播效果，可以使用vue-awesome-swiper，因此就开始了一系列的踩坑之旅。遇到的问题（坑）安装模块点击进入vue-awesome-swiper的github主页，就可看到黄色的提示以及官方介绍，说是由于Swiper官网对vue的支持，这个库已经弃用了，当时看到这个提示的时候并没有细想（确实是有点急），看了如下的版本兼容性列表后发现 vue-awesome-swiper是支持项目所使用的Vue2的，于是就想着那就用它了。Swiper 5-6 vue-awesome-swiper@4.1.1 (Vue2)Swiper 4.x vue-awesome-swiper@3.1.3 (Vue2)Swiper 3.x vue-awesome-swiper@2.6.7 (Vue2)点击进去vue-awesome-swiper@4.1.1的github主页之后，看到了官方给的安装指令，心想真好，一顿CV执行了命令yarn add swiper vue-awesome-swiper，甚至都没有多看一眼但是很顺利安装上了vue-awesome-swiper。组件调用根据官方的引导进入了示例网站Examples (Vue2)，在里面找到了一个与想要效果类似的示例，并将代码复制到项目中测试运行，首先是导入组件：import { Swiper, SwiperSlide } from 'vue-awesome-swiper' import 'swiper/css/swiper.css'然后IDE报错，说是swiper.css找不到，其实这时就应该有所察觉的，但当时就想着怎么官网实例都有问题呢，可真不靠谱。在晚上一顿搜索说是，Swiper不同的版本swiper.css的位置可能是不一样的（这会竟然还没意识到Swiper的版本有问题），然后修改了正确的地址，没报错了。 <swiper class="swiper" :options="swiperOption"> <swiper-slide>Slide 1</swiper-slide> <swiper-slide>Slide 2</swiper-slide> <swiper-slide>Slide 3</swiper-slide> <swiper-slide>Slide 4</swiper-slide> <swiper-slide>Slide 5</swiper-slide> <swiper-slide>Slide 6</swiper-slide> <swiper-slide>Slide 7</swiper-slide> <swiper-slide>Slide 8</swiper-slide> <swiper-slide>Slide 9</swiper-slide> <swiper-slide>Slide 10</swiper-slide> </swiper> 接着把上面的代码复制进行了项目中，也成功运行了，并且没有任何报错，但是出来的效果没有和官网效果图一样，代码中slidesPerView设置3含义是容器中同时会出现3个swiper-slide，如下图所示。但实际上运行出的效果只有一个swiper-slide，之后又对比了与官方代码的区别，并没有什么不同，这就导致对官方代码暂时失去了信任。初步实现效果于是就在网上搜索了vue-awesome-swiper的相关用法，最终找到了能实现类似效果的一篇文章，那篇文章中的代码和官方代码相比，除了swiperOption配置外，最大的区别就是添加了一些自定义的css样式，使其实现了想要的效果。但是一个最大的问题就是分页器不显示，配置中的pagination属性就描述了分页器的相关信息，最初以为是配置写错了，但是经过一番检查后还是没发现问题，而且还用了旧版本的pagination写法也还是不行。通过Chrome的调试工具可发现，模板中写的pagination的div里面是空的，但是官方运行的实例里面是有元素的，也就是说pagination…