xccjk / x-blog

学习笔记
17 stars 2 forks source link

相关代码片段 #70

Open xccjk opened 2 years ago

xccjk commented 2 years ago

video播放相关

video播放结束事件ended监听不生效

当video设置了循环播放属性loop时,监听ended事件不会生效

<video id="video" loop>
  <source type="video/mp4" loop src="xxx.mp4">
</video>

const ele = document.getElementById('video');
ele.addEventListener('ended', () => {
    console.log('播放结束');
});

上面的代码在视频播放结束后,控制台不会打印播放结束

要想在视频设置了循环播放,同时获取是否播放结束,可以结合timeupdateloadedmetadata来实现

<video id="video" loop>
  <source type="video/mp4" loop src="xxx.mp4">
</video>

const ele = document.getElementById('video');

let maxTime = 0;
ele.addEventListener('loadedmetadata', () => {
  maxTime = ele.duration;
  console.log('获取视频时长');
});

ele.addEventListener('timeupdate', () => {
  const { currentTime } = ele;
  if (currentTime + 0.2 > maxTime) {
    console.log('视频播放结束了');
  }
}, false);
xccjk commented 2 years ago

浏览器端实现点击按钮下载图片

移动端不兼容 移动端不兼容 移动端不兼容

老版本Google浏览器可以a标签直接下载,新版本会打开图片,只有图片同源才会直接下载 老版本Google浏览器可以a标签直接下载,新版本会打开图片,只有图片同源才会直接下载 老版本Google浏览器可以a标签直接下载,新版本会打开图片,只有图片同源才会直接下载

现在新版的浏览器,点击a标签进行下载图片时,往往不会按照预期实现图片的下载功能,而是会在新的窗口打开图片,代码如下:

<a href="https://static001.geekbang.org/resource/image/fa/af/face2257c62b291620a1750b4cdaf4af.jpg?x-oss-process=image/resize,m_fill,h_400,w_818" download>下载</a>

要想实现点击按钮下载图片,常见的做法是后端配置请求头来实现资源的下载,但是现在一般都前后端分离了,并且很多图片在oss上,不能直接修改通用的请求配置

当请求的图片是同源的时,可以直接通过a标签实现下载

// 网址
https://www.xxx.com
// 图片地址
https://www.xxx.com/image/1.png

当图片是cdn地址时,配置图片的请求头为当前域名也可以

// 极客时间地址
https://time.geekbang.org/
// 极客时间图片地址
https://static001.geekbang.org/resource/image/ab/9f/ab17ecf8fdb768db1362ec72c2f8ce9f.jpg
// 图片请求头配置
referer: https://time.geekbang.org/

2

当图片不再跨域时,可以使用的方法就比较多了:

通过blob流来实现下载:

const imgDownload = (src, name) => {
  const canvas = document.createElement('canvas');
  const img = document.createElement('img');
  img.onload = () => {
    canvas.width = img.width;
    canvas.height = img.height;
    const context = canvas.getContext('2d');
    context.drawImage(img, 0, 0, img.width, img.height);
    canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
    canvas.toBlob(
      (blob) => {
        const link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = name || 'download'; // resource name
        link.click();
      },
      '0.95',
    );
  };
  img.setAttribute('crossOrigin', 'Anonymous');
  img.src = src;
}

imgDownload(url, 1.png);

通过FileSaver.js实现下载

import { saveAs } from 'file-saver';

saveAs(url, 1.png)
xccjk commented 2 years ago

浏览器实现批量下载在线图片到本地同一个文件夹并压缩

import JSZip from 'jszip';
import { saveAs } from 'file-saver';

const imgBlob = (src) => {
  const canvas = document.createElement('canvas');
  const img = document.createElement('img');
  img.setAttribute('crossOrigin', 'Anonymous');
  img.src = src;
  return new Promise((resolve) => {
    img.onload = () => {
      canvas.width = img.width;
      canvas.height = img.height;
      const context = canvas.getContext('2d');
      context.drawImage(img, 0, 0, img.width, img.height);
      canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
      canvas.toBlob(
        (blob) => {
          resolve(blob);
        },
        '0.95',
      );
    };
  });
};

const downloadImgs = async (urls, name) => {
    const zip = new JSZip();
    const imgs = zip.folder('images');

    const blobs = urls.map(async (url) => {
      const blob = await imgBlob(url);
      return blob;
    });
    blobs.forEach((blob, index) => {
      imgs.file(`${index}.png`, blob);
    });
    zip.generateAsync({ type: 'blob' }).then((content) => {
      saveAs(content, `${name || 'example'.zip}`);
    });
  };
xccjk commented 2 years ago

echarts调整图片清晰度

方法1

调整像素比参数devicePixelRatio,devicePixelRatio值越大清晰度越高

const chartDom = document.getElementById('main');
const myChart = echarts.init(chartDom, null, { devicePixelRatio: 15 });

方法2

调整生成图片类型为SVG,这样在缩放时就不会失真了

const chartDom = document.getElementById('main');
const myChart = echarts.init(chartDom, null, { renderer: 'svg' });

1

xccjk commented 2 years ago

实现原生flat

const arr = [1, [2, 3], [4, 5, [6]], [7, 8, [9, [10]]]];
let i = 0;
function fn5(array, index = Infinity) {
  let result1 = [];
  if (index === Infinity) {
    array.forEach((item) => {
      if (Array.isArray(item)) {
        result1 = [...result1, ...item]
      } else {
        result1.push(item)
      }
    })
    if (!array.some(item => Array.isArray(item))) return array;
    return fn5(result1, Infinity);
  } else {
    index = Number(index)
    if (!index) return array;
    if (i <= index && !array.some(item => Array.isArray(item))) return array;
    array.forEach((item) => {
      if (Array.isArray(item)) {
        result1 = [...result1, ...item]
      } else {
        result1.push(item)
      }
    })
    i++;
    if (i === index) return result1;
    return fn5(result1, index);
  }
}

console.log(fn5(arr, true))
xccjk commented 1 year ago

remove NodeList

// 获取指定类型所有节点
const nodes = document.querySelectorAll('audio[preload]');
// NodeList转数组
const  arr = [].slice.apply(nodes);
// 移除节点
arr.forEach(node => node?.remove());
xccjk commented 1 year ago

HTML字符串渲染到页面

innerHTML

<div id="app"></div>

document.getElementById('app').innerHTML = '<h1>1024</h1>'

dangerouslySetInnerHTML

<div
    dangerouslySetInnerHTML={{
      __html: '<h1>1024</h1>'
  />

dome: codesandbox

xccjk commented 1 year ago

ofd格式文件渲染

通过三方服务https://view.xdocin.com来进行ofd格式文件展示

<iframe width="100%" height={600} src={`https://view.xdocin.com/view?src=${src}`} />

ofd.js

前端ofd预览库,问题不少,对于很多ofd格式文件渲染不完整。官方demo中的几个文件确实可以,但是很多其它ofd文件好像是不完整的

github

后端方案

github

xccjk commented 1 year ago

js判断浏览器是否支持flash渲染

export const hasUsableSWF = () => {
  let swf;
  if (typeof window?.ActiveXObject !== 'undefined') {
    swf = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
  } else {
    swf = navigator.plugins['Shockwave Flash'];
  }
  return !!swf;
};
xccjk commented 1 year ago

js时间秒数转为时间格式

例:2000秒转为时间格式为33:20

function formateTime(time) {
  const h = parseInt(time / 3600, 10);
  const minute = parseInt((time / 60) % 60, 10);
  const second = Math.ceil(time % 60);

  const hours = h < 10 ? '0' + h : h;
  const formatSecond = second > 59 ? 59 : second;
  return `${hours > 0 ? `${hours}:` : ''}${minute < 10 ? '0' + minute : minute}:${
    formatSecond < 10 ? '0' + formatSecond : formatSecond
  }`;
}
formateTime(2000)  // '33:20'
xccjk commented 1 year ago

判断是否微信环境

export const isWeixin = () => {
  return /MicroMessenger|WeXin|WeChat/g.test(navigator.userAgent);
};
xccjk commented 1 year ago

清理所有cookie

export const clearCookie = () => {
  const keys = document.cookie.match(/[^ =;]+(?=\=)/g);
  if (keys) {
    for (let i = keys.length; i--; ) document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
  }
};
xccjk commented 1 year ago

图片预加载

export const preload = (arr) => {
  for (let i = 0; i < arr.length; i++) {
    const img = new Image();
    img.src = arr[i];
  }
};
xccjk commented 1 year ago

JavaScript点击事件后实现多文件下载

xccjk commented 1 year ago

跳转页面后滚动条到顶部

useEffect(() => {
  document.body.scrollTop = 0;
  document.documentElement.scrollTop = 0;
}, []);
xccjk commented 1 year ago

ios h5 获取焦点时页面放大

通过添加meta标签来控制页面缩放

<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
  name="viewport"
  content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
xccjk commented 1 year ago

常见的代码逻辑优化处理

多条件判断

  // bad
  function filter(type) {
    if (type === 1 || type === 2 || type === 3 || type === 4 || ...) {
      console.log('条件成立了...')
    }
  }
  // good
  const types = [1, 2, 3, 4, ...]
  function filter(type) {
    if (types.includes(type)) {
      console.log('条件成立了...')
    }
  }

检查数组是否都满足某条件

  const data = [
    { name: 'a', age: 20 },
    { name: 'b', age: 28 },
    { name: 'c', age: 18 }
  ]
  // bad
  function filter(n) {
    let isAll = true
    data.forEach(({ age }) => {
      if (age > n) {
        isAll = false
      }
    })
    return isAll
  }
  // bad
  function filter(n) {
    const o = data.find(x => x.age > n)
    return o ? true : false
  }
  // good
  function filter(n) {
    return data.every(x => x.age > n)
  }

数组中某一项满足要求

  const data = [
    { name: 'a', age: 20 },
    { name: 'b', age: 28 },
    { name: 'c', age: 18 }
  ]
  // bad
  let isAge = false
  data.forEach(({ age }) => {
    if (age > 25) {
      isAge = true
    }
  })
  // good
  const isAge = data.some(({ age }) => age > 25)

对数组中匹配上的某个值进行解构

  const data = [
    { name: 'a', age: 20 },
    { name: 'b', age: 28 },
    { name: 'c', age: 18 }
  ]
  // bad
  const { name, age } = data.filter(({ age }) => age > 25)[0] || {}
  // good
  const { name, age } = data.find(({ age }) => age > 25) || {}

函数默认值

  // bad
  function f(m) {
    return m || 1
  }
  // good
  function f(m = 1) {
    return m
  }

解构匹配 - 可选链操作符

  const obj = { a: { b: { c: 1 } } }
  // bad
  const { a = {} } = obj
  const { b = {} } = a
  const { c } = b
  // good
  // 需要配置babel
  const m = a?.b?.c
  // bad
  <div>
    {
      (this.props.data || []).map(li => <span>{li}</span>)
    }
  </div>
  // good
  <div>
    {
      this.props?.data?.map(li => <span>{li}</span>)
    }
  </div>

多条件匹配

  // bad
  function pick(type) {
    if (type === 1) {
      return [0, 1]
    } else if (type === 2) {
      return [0, 1, 2]
    } else if (type === 3) {
      return [0, 3]
    } else {
      return []
    }
  }
  // bad
  function pick(type) {
    switch (type) {
      case 1:
        return [0, 1]
      case 2:
        return [0, 1, 2]
      case 3:
        return [0, 3]
      default:
        return []
    }
  }
  // good
  // 枚举法
  const fn = {
    1: [0, 1],
    2: [0, 1, 2],
    3: [0, 3]
  }
  const filter = fn[type] ?? []
  // good
  // map数据结构
  const fn = new Map()
    .set('1', [0, 1])
    .set('2', [0, 1, 2])
    .set('3', [0, 3])
  const filter = fn[type] ?? []

三元表达式优化

  // bad
  return (
    <>
      {
        isCheck ? (
          <div>check all</div>
        ) : null
      }
    </div>
  )
  // good
  if (!isCheck) return null
  return <div>check all</div>

数字转换中常见的错误处理 - ~~运算符

  // bad
  function sum(num) {
    return +num + 1
  }
  // bad
  function sum(num) {
    return Number(num) + 1
  }
  sum('1.1')  // 2.1
  sum('1.1abc') // NaN
  // good
  function sum(num) {
    return ~~num + 1
  }
  sum('1.1')  // 2.1
  sum('1.1abc') // 1

错误处理 - try...catch...

  // bad
  function async fetchData() {
    const res = await getData({})
    const { success, response = [] } = res || {}
    if (success) {
      setData(response)
    }
  }
  // good
  function async fetchData() {
    try {
      const res = await getData({}) ?? []
      const { success, response = [] } = res
      if (success) {
        setData(response)
      }
    } catch (err) {
      console.log('error', const res = await getData({}))
    }
  }
xccjk commented 1 year ago

h5开发中常见问题

  1. 在设置页面禁止复制文本是,设置了-webkit-user-select: none导致iOS手机上输入框类失效

    // index.html
    * {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    }
    // 排除input与textarea
    [contenteditable="true"], input, textarea {
    -webkit-user-select: auto!important;
    -khtml-user-select: auto!important;
    -moz-user-select: auto!important;
    -ms-user-select: auto!important;
    -o-user-select: auto!important;
    user-select: auto!important;
    }
  2. iPhoneX底部样式兼容,包括页面主体内容的在安全区域及fix定位下bottom: 0的兼容。参考:iPhone X兼容

  3. h5中拨号,采用window.open('tel:15000000000'),在iOS中不能使用。原因:iOS中不兼容window.open方法,通过window.location.href = 'tel:15000000000'来实现拨号操作

    
    const isIos = function() {
    const isIphone = navigator.userAgent.includes('iPhone')
    const isIpad = navigator.userAgent.includes('iPad')
    return isIphone || isIpad
    }

if (isIos) { window.location.href = 'tel:15000000000' } else { window.open('tel:15000000000') }


4. 键盘遮挡输入框,onBlur方法监听处理

<input id='phone' type='tel' maxLength='11' placeholder='请输入' onChange={({ target: { value } }) => handlePhone(value)} onBlur={() => inputOnBlur()} />

const check = () => { setTimeout(() => { const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0 window.scrollTo(0, Math.max(scrollHeight - 1, 0)) }, 100) }

const inputOnBlur = () => { document.getElementById('phone').setAttribute('onblur', check())

setTimeout(() => { window.scrollTo(0, document.body.scrollTop + 1) document.body.scrollTop >= 1 && window.scrollTo(0, document.body.scrollTop - 1) }, 100) }


5. 在react中使用类keep-alive组件在pc/h5中的使用 - react-live-route
- 场景:长列表中滚动到很多页后查看了某条数据,进入列表详情,返回到列表页是,会回到顶部
- 原因:react中进行路由跳转时,state等数据会丢失,也不回记录滚动等信息
- 解决方式:
      - 采用redux等数据流工具,在列表页面跳转的时候在componentWillUnmount生命周期记录state中的数据与滚动位置信息,在componentDidMount生命周期对数据进行恢复、
      - 在路由跳转的时候隐藏列表页,在回到列表页的时候再重新渲染出来
- 遇到的问题
      - Switch渲染的是匹配到的第一个路由,而LiveRoute是为了让组件强制渲染不匹配的路由
      - Switch与LiveRoute应包裹在同一个div中,不然会报错A <Router> may have only one child element
      - 采用position: absolute进行定位的元素会有影响
      - routes中的路由应该与keepRouter中的路由不重复,重复的情况下会在同一个路由下渲染页面两次
- 参考链接
      - https://github.com/facebook/react/issues/12039
      - https://github.com/fi3ework/react-live-route
      - https://codesandbox.io/s/yj9j33pw4j?file=/src/index.js:399-409
      - https://zhuanlan.zhihu.com/p/59637392

npm install react-live-route --save

// routes.js export const routes = [ { path: '/', exact: true, component: () => }, / { path: '/list', exact: true, component: () => }, / { path: '/create', exact: true, component: () => (

)

} ] // 需要处理的路由列表 export const keepRouter = [ { path: '/list', component: DirectListPage }, { path: '/demand/list', component: DemandListPage } ]

// APP.js import NotLiveRoute from 'react-live-route' import { routes, keepRouter } from './pages/routes'

const LiveRoute = withRouter(NotLiveRoute)

<> {routes.map((item, index) => { return ( { if (canDirectLogin === true) { return } else if (canDirectLogin === false) { return } else { return } }} /> ) })} {keepRouter.map((item, index) => { return ( ) } )}
6. 微信H5重复授权
- 问题描述
      - android手机上,主要在低版本的android机上,客户打开微信H5,页面出现多次弹出授权窗口,需要点击多次确认才会消失
- 问题原因
      - 多次重定向导致出现多次授权窗口
      - hash模式路由导致的参数丢失
      - 授权链接中参数不完整

// 最终的重定向方法 // 去除重定向地址中的#,同时添加参数connect_redirect redirectWXAuth = () => { const { goToPage } = this.state const url = (goToPage + '').replace('#', '') const redirectUrl = encodeURIComponent( ${process.env.REDIRECT_HOST}/login?goto_page=${encodeURIComponent(url)}&bindCode=1 ) const wechatAuthUrl = https://open.weixin.qq.com/connect/oauth2/authorize?appid=${process.env.WXAPPID}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect window.location.replace(wechatAuthUrl) }

xccjk commented 1 year ago

pc端分享

const qqShare = ({ url = window.location.href, desc, title, summary, pics }) => {
  const urlPath = `https://connect.qq.com/widget/shareqq/index.html?url=${encodeURI(
    url
  )}&desc=${desc}&title=${title}&summary=${summary}&pics=${pics}`;
  window.open(urlPath);
};

const wbShare = ({ url = window.location.href, desc, title, summary, pics }) => {
  const urlPath = `http://service.weibo.com/share/share.php?url=${encodeURI(
    url
  )}&desc=${desc}&title=${title}&summary=${summary}&pics=${pics}`;
  window.open(urlPath);
};