Skip to content

问题总结(一)

正则表达式的坑点?

javascript
const txt1 = 'abc'
const txt2 = 'abc'
const regexp = new RegExp('a', 'g')

console.log(regexp.test(txt1)) // true
console.log(regexp.test(txt2)) // false

// 因为使用了 全局查找 g 后,正则表达式会记录 lastIndex,当判断 txt1后,lastIndex已经修改为1,因为判断txt2时会查找不到

// 解决方法
// 1. 重置 lastIndex
console.log(regexp.test(txt1)) // true
regexp.lastIndex = 0 // 重置 lastIndex
console.log(regexp.test(txt2)) // true

// 2. 分别使用不同的正则对象判断
const regexp1 = new RegExp('a', 'g')
const regexp2 = new RegExp('a', 'g')
console.log(regexp1.test(txt1)) // true
console.log(regexp2.test(txt2)) // true

样式横竖屏适配?

css
@media screen and (orientation: portrait){
  /* 竖屏样式 */
}

@media screen and (orientation: landscape){
  /* 横屏样式 */
}

js监听横竖屏状态

javascript
function orient() {
  if (window.orientation == 90 || window.orientation == -90) {
    // 横屏
  } else if (window.orientation == 0 || window.orientation == 180) {
    // 竖屏 
  }
}

window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', orient, false)
// 注意:
// orientationchange 只有移动端有,pc端没有
// 手动设置 window.orientation 的值并不能达到切换横竖屏。h5只能监听到,无法控制。

h5强制横屏显示

原理是利用屏幕的宽高以及css的旋转来进行模拟

javascript
// 自动切换横竖屏
// query 是解析url中的参数,其中有一个参数screen_type,为1时表示强制横屏,其余表示不限定
function detectOrient(query) {
  const width = document.documentElement.clientWidth // 获取宽度
  const height = document.documentElement.clientHeight // 获取高度
  let style = {}
	
  if (width < height) {
    // 当前手机处于竖屏情况
    // 是否强制横屏
    if (query.screen_type == 1) {
      style = {
        'width': `${height}px`,
        'height': `${width}px`,
        'transform-origin': `${width / 2}px ${width / 2}px`,
        '-webkit-transform-origin': `${width / 2}px ${width / 2}px`,
        'transform': 'rotate(90deg)',
      }
	  // 这一步主要是解决旋转后,手机标识是竖屏,但我需要使用横屏下的rem(主要是导致css样式大小的不同)
      $('html').css({
        'font-size': `${window.refreshRem(height)}px`
      })
    }
  } else {
    if (query.screen_type == 1) {
      style = {
        'width': `${width}px`,
        'height': `${height}px`,
        'transform-origin': '0 0',
        '-webkit-transform-origin': '0 0',
        'transform': 'rotate(0deg)',
      }
      $('html').css({
        'font-size': `${window.refreshRem(width)}px`
      })
    }
  }

  $('.try-wrap').css(style) // 旋转操作,兼容手机处于横屏后,需要恢复不需要旋转
}

$(window).resize(function () {
    detectOrient(query)
})

以上主要是针对单元素强制横屏,如果涉及到多个元素,最好的方法是选择当前页面的html 标签。

注意

使用这种模拟方法,很大的弊端就是,不能使用样式@media screen 来监听修改样式,以及css定位问题,包括css中刘海屏的处理方法也没法用。特别对悬浮球这种是致命的。

如:强制横屏后(即需要旋转90deg),当前手机却依旧竖屏状态,如果这时把手机切为横屏状态,则需要把原先选择的元素旋转回来。

刘海屏适配?

安全区域

安全区域指的是一个可视窗口范围,处于安全区域的内容不受圆角(corners)、齐刘海(sensor housing)、小黑条(Home Indicator)影响,如下图蓝色区域:

viewport-fit

iOS11 新增特性,苹果公司为了适配 iPhone X 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置三个值👇

  • contain: 可视窗口完全包含网页内容(左图)
  • cover:网页内容完全覆盖可视窗口(右图)
  • auto:默认值,跟 contain 表现一致

第一步

适配第一步,必须将 viewport metaviewport-fit 设置为 cover,否则下面的设置不会生效。

如:<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">

函数

iOS11 新增特性,Webkit 的一个 CSS 函数👇

  • env()
  • constant()

The env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology Preview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use the CSS fallback mechanism to support both versions, if necessary, but should prefer env() going forward.

意思是 constant() 在 iOS11.2 之后就不能使用的。

用于设定安全区域与边界的距离,有四个预定义的变量👇

  • safe-area-inset-left:安全区域距离左边边界距离
  • safe-area-inset-right:安全区域距离右边边界距离
  • safe-area-inset-top:安全区域距离顶部边界距离
  • safe-area-inset-bottom:安全区域距离底部边界距离

PS:横竖屏的时候值都不相同

举例👀

css
padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

PS:只会在刘海屏的时候生效,其余情况不生效

网站暗黑模式???

css
/* 添加滤镜效果,配色反转,色相改变 */
html[theme='dark-mode'] {
	filter: invert(1) hue-rotate(180deg);
}

/* 因为上面的规则会把图片也改变了,所以又将图片返回来 */
html[theme='dark-mode'] img {
	filter: invert(1) hue-rotate(180deg);
}

/* 添加样式改变时的过渡 */
html {
	transition: color 300ms, background-color 300ms;
}

原来对比暗黑后的样子👇

还有另一种暗黑模式的方法就是引入不同的 theme.css,这种方式是最好的。

压缩图片?

利用的是 canvas 修改图片质量原理来达到压缩。使用于大部分图片格式,gif除外。

其中几个比较重要的方法,插眼,自己传送吧👉

传入两个参数:一文件,二质量(默认0.7),返回结果为一个对象。

  • response
    • code 状态码
    • message 消息
    • state 当前状态 done 完成,error错误
    • file 压缩后的文件(默认会添加一个info属性,包含图片的宽高)
javascript
/**
 * @description: 将base64转换为文件
 * @param {string} dataurl 文件base64
 * @param {string} filename 文件名
 * @return {file} 
 */
function dataURLtoFile(dataurl, filename = 'demo') {
  const arr = dataurl.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n > 0) {
    n -= 1
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new File([u8arr], filename, {
    type: mime
  })
}

/**
 * @description: 压缩图片
 * @param {file} file 文件
 * @param {number} quality 图片质量
 * @return {file} file 压缩后的文件
 */
function compress(file, quality = 0.7) {
  return new Promise((resolve, reject) => {
    try {
      // 读取文件
      const reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onload = function () {
        // 设置图片
        const img = new Image()
        img.src = this.result

        img.onload = function () {
          const self = this
          const w = self.width // 图片宽度
          const h = self.height // 图片高度
          // 创建并初始化canvas
          const canvas = document.createElement('canvas')
          const ctx = canvas.getContext('2d')
          // 设置属性
          const anw = document.createAttribute('width')
          anw.nodeValue = w
          canvas.setAttributeNode(anw)
          const anh = document.createAttribute('height')
          anh.nodeValue = h
          canvas.setAttributeNode(anh)
          // 绘图
          ctx.drawImage(self, 0, 0, w, h)
          // 压缩
          const base64 = canvas.toDataURL(file.type, quality)
          // base64转file
          const imgFile = dataURLtoFile(base64, file.name)
          // 添加属性
          imgFile.uid = file.uid || 0
          imgFile.info = {
            width: w,
            height: h
          }
          const result = {
            code: 200,
            state: 'done',
            message: '成功',
            file: imgFile
          }
          resolve(result)
        }
      }
    } catch (e) {
      const result = {
        code: 200,
        state: 'error',
        message: '转换失败'
      }
      reject(result)
    }
  })
}

/**
 * @description: 压缩图片hooks
 * @param {file} file 文件
 * @param {quality} quality 质量
 * @return {type} 
 */
const compressImg = (file, quality) => {
  return compress(file, quality)
}

export default compressImg

CSS3 pointer-events

📖 pointer-events

pointer-events 大多数值都是只用 svg。这里主要讲一下 pointer-events: auto|noneauto 就不用多讲了,默认值;而当为none 时候,它会把元素的点击事件给禁止掉,比如什么链接、点击呀都变成浮云。

但它可以使用 Tab 键进行选中触发点击。

它的作用可以实现“幻影”特效。👇

判断浏览器是否支持pointer-events

javascript
var supportsPointerEvents = (function(){
  var dummy = document.createElement('_');
  if(!('pointerEvents' in dummy.style)) return false;
  dummy.style.pointerEvents = 'auto';
  dummy.style.pointerEvents = 'x';
  document.body.appendChild(dummy);
  var r = getComputedStyle(dummy).pointerEvents === 'auto';
  document.body.removeChild(dummy);
  return r;
})();

CSS user-select

📖 user-select

CSS属性 user-select 控制用户能否选中文本。除了文本框内,它对被载入为 chrome 的内容没有影响。

  • none 文本不能选中。
  • auto 根据不同情况采用不同的值。
  • text 可以选择文本。
  • all 双击子元素或者点击元素选中。
  • contain 允许在元素内选择;但是,选区将被限制在该元素的边界之内。

CSS aspect-ratio

为box容器规定了一个期待的纵横比。然后规定任意一边的值,则另一边将按比值自动换算。 这个是一个比较新的属性,可以通过 Can I Use 查看目前浏览器支持度。

css
aspect-ratio: <ratio>;
// 例:
aspect-ratio: 1 / 1;
aspect-ratio: 9 / 16;

在此之前,实现能纵横比还有一种做法,使用 padding-*padding-* 属性的百分比是基于自身宽度百分比。

💡例如实现一个 9 比 16 的 div

css
width: 100px;
padding-bottom: 160%;
background-color: #000;

CSS scroll-behavior

📖 scroll-behavior

scroll-behavior 为一个滚动框指定滚动行为,但用户行为而产生的滚动,不受其影响,即滚轮或主动拖拽滚动条等。

css
scroll-behavior: <auto | smooth>;
  • auto 滚动框立即滚动。
  • smooth 滚动框通过一个用户代理预定义的时长、使用预定义的时间函数,来实现平稳的滚动。

视频全屏及画中画

js
/**
 * @description: 控制全屏
 * @param {Element} el 待全屏dom对象
 * @return {Promise}
 */
export const toFullVideo = el => {
  if (el.requestFullscreen) {
    return el.requestFullscreen()
  } else if (el.webkitRequestFullScreen) {
    return el.webkitRequestFullScreen()
  } else if (el.mozRequestFullScreen) {
    return el.mozRequestFullScreen()
  } else {
    return el.msRequestFullscreen()
  }
}

/**
 * @description: 退出全屏
 */
export const exitFullscreen = () => {
  if (document.exitFullscreen) {
    document.exitFullscreen()
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen()
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen()
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen()
  }
}

/**
 * @description: 开启画中画
 * @param {Element} el 视频对象
 * @return {Promise}
 */
export const toPicInPic = el => {
  return el.requestPictureInPicture().catch(() => {
    console. error('The video failed to open picture in picture')
  })
}

/**
 * @description: 关闭画中画
 */
export const exitPicInPic = () => {
  document.exitPictureInPicture().catch(() => {
    console.error('The video failed to close picture in picture')
  })
}

监听是否全屏

javascript
videoWrapRef.value.addEventListener('fullscreenchange', fn)

监听是否画中画

javascript
videoRef.value.addEventListener('enterpictureinpicture', fn)
videoRef.value.addEventListener('leavepictureinpicture', fn)

轻量级视频播放器vue版 iu-video 👉 https://www.npmjs.com/package/iu-video

移动端 文件上传类型限制?

<input type="file"> 中有一个属性 accept ,接收一个包含一个或多个(用逗号分隔)唯一文件类型说明符的字符串,用来指定 input 接收的类型。

🔔 它有两种写法:

html
<!-- 这两个 input 都限制 只接收图片 jpeg 类型 -->
<input type="file" accept=".jpeg">
<input type="file" accept="image/jpeg">

但如果你在移动端 H5 中使用第一种写法,很可能是没有作用的,而当你切换为第二种写法(✅推荐)时,神奇响应了。

列举一些常见的文件类型
mp4格式文件 		video/mp4
mov格式文件 		video/quicktime
webm格式文件 		video/webm
rmvb格式文件		application/vnd.rn-realmedia-vbr
jpg/jpeg格式文件	image/jpeg
gif格式文件			image/gif
png格式文件			image/png

Base64 编码原理

例:abcd 转 base64, 得到的结果是:YWJjZA==

  1. 将每个字符三三分组,不足补零。即 abc d00
  2. 将每个字符转为 ASCII 码。即 97 98 99 100 0 0
  3. 将 ASCII 码转为(8位)二进制,即 01100001 01100010 01100011 01100100 00000000 00000000,再把每六位分组隔开,即011000 010110 001001 100011 011001 000000 000000 000000
  4. 最后根据彩虹表将二进制转为字符,补位0产生=。即 YWJjZA==

彩虹表

js 复制到剪贴板

javascript
function copyText(text) {
  if (navigator.clipboard) {
    navigator.clipboard.writeText(text);
  } else {
    const textarea = document.createElement('textarea');
    document.body.appendChild(textarea);
    textarea.style.position = 'fixed';
    textarea.style.clip = 'rect(0 0 0 0)';
    textarea.style.top = '10px';
    textarea.value = text;
    textarea.select();
    document.execCommand('copy', true);
    document.body.removeChild(textarea);
  }
}

在我的 github toolbox 查看更多通用方法

但这节主要的重点不是它如何实现,而是这里有一个坑点⚠️

❌在 Android webview 中会报一个 write permission denied. 错误

意思就是没有写入的权限,由 navigator.clipboard.writeText(text) 抛出。 使用这个 API 是需要开启权限的,但在 Android webview 中又没有提供,因此这个在 Android webview 中是失败的。 目前的解决方法唯有 调用 客户端的方法,由它们来写入到剪贴板📋。

Taro v3 开发小程序遇到的相关问题?

生命周期

// react 生命周期
componentWillMount() {} // 2. 页面组件渲染到 Taro 的虚拟 DOM 之前触发
componentDidMount() {} // 3. 页面组件渲染到 Taro 的虚拟 DOM 之后触发
componentUnMount() {} // 8. 组件卸载触发

// 小程序页面 生命周期
onLoad() {} // 1. 页面加载
componentDidShow() {} // 4. 后台切前台or页面显示
onReady() {} // 5. 页面就绪
componentDidHide() {} // 前台切后台
onUnload() {} // 7. 页面卸载
render() {} // 6.

// PS: 序号为执行顺序

// v3 版本函数改名
// componentWillMount -> UNSAFE_componentWillMount
// componentWillReceiveProps -> UNSAFE_componentWillReceiveProps
// componentWillUpdate -> UNSAFE_componentWillUpdate

组件获取小程序真实dom方法

$scope, $componentType 废弃,因为 v3 版本没有自定义组件,所以组件内获取原生 DOM 统一使用 createSelectorQuery, 注意组件内中使用需要延迟,页面组件不需要(因为可在 onReady 获取)

javascript
// v1, v2
Taro.createSelectorQuery().in(this.$scope).select()

// v3
Taro.createSelectorQuery().select()

web-view 通信

网页想小程序 possMessage 时,只会在特定时机(后退,组件销毁,分享等)触发才收到信息,即没有触发这些情况时,无论你发送多少次小程序都收不到。目前想通信,唯有靠 url

自定义tabBar 注意点?

必须是 src/custom-tab-bar 目录,不能自定义其他名字的目录,并且 app.js 需指定 tabBar: { custom: true, list: [] }

自定义 TabBar 在各个 TabBar 页面初始化时都会创建一个新的组件实例。 因此有两种状态管理方法:

  1. getTabBar 管理状态
  2. 状态管理工具,如 vuexredux

注意

如果自定义tabBar 是使用函数组件来编写的话,在 Tab 页 componentDidShow 方法中,通过

javascript
const pageObj = Taro.getCurrentInstance().page
const tabbar = Taro.getTabBar(pageObj)

获取 tabBar 的实例会始终为 undefined。倘若改成 class 组件的话即可获取到。目前暂未知道问题出在哪里。

Vite.config.ts 配置文件中使用环境变量的问题

当在 Vite 中使用环境变量 import.meta.env.*

  1. 如果你使用的是 typescript,会出现一个错误❌ property 'env' does not exist on type 'importmeta',这个只需要在 tsconfig.json 添加 "types": ["vite/client"] 即可。
  2. 当你解决了上面那个类型错误时,发现还是报错,找不到改变量。因为在这些环境变量无法再配置文件中使用

解决方案有两种

  • 使用 process.env.* 来代替 import.meta.env.*

  • 配置中 defineConfig 传入一个函数,如 export default defineConfig(({ command, mode, ssrBuild }) => {})

fix: https://github.com/vitejs/vite/issues/512

Released under the MIT License.