[TOC]
# 功能
## buffer-queue
```
site/wechat-react/components/tools/buffer-queue.js
```
## socket-handle
```
site/wechat-react/components/tools/ql-socket-handle.js
```
## 获取滑动方向
```javascript
//获取滑动方向
var getScrollDirection = function() {
//返回角度
function GetSlideAngle(dx,dy) {
return Math.atan2(dy,dx) * 180 / Math.PI;
}
//根据起点和终点返回方向
function GetSlideDirection(startX,startY, endX, endY) {
var dy = startY - endY;
var dx = endX - startX;
var result = 0;
//如果滑动距离太短
if (Math.abs(dx) < 2 && Math.abs(dy) < 2) {
return result;
}
//45到135 向上滑动 -180到0向下滑动
var angle = GetSlideAngle(dx, dy);
if (angle >= 45 && angle < 135) {
$('.nav').fadeOut();
}else if(angle >= -135 && angle < -45){
$('.nav').fadeIn();
}
}
//滑动处理
var startX, startY;
document.addEventListener('touchstart', function (ev){
startX = ev.touches[0].pageX;
startY = ev.touches[0].pageY;
}, false);
document.addEventListener('touchmove', function (ev){
var endX, endY;
endX = ev.changedTouches[0].pageX;
endY = ev.changedTouches[0].pageY;
GetSlideDirection(startX, startY, endX, endY);
}, false);
}
```
## 图片缩放
```javascript
/*
* 缩放、居中图片
* image [dom] jquery元素
* scaleWidth [number] 缩放后的宽
* scaleHeight [number] 缩放后的高
* middle [boolean] 是否居中
*
*/
function imgScale(image, scaleWidth, scaleHeight, middle) {
//获取图片的原始宽高和比例
var originWidth = image[0].naturalWidth || image[0].width;
var originHeight = image[0].naturalHeight || image[0].height;
var ratio = originWidth / originHeight;
//如果原图宽比高大
if(ratio > 1) {
//如果缩放后的宽度比要求的宽度要小,把宽度缩放为要求的宽度
if(originWidth/(originHeight/scaleHeight) < scaleWidth) {
image.width(scaleWidth);
//否则把图片的高度缩放为要求的高度
}else {
image.height(scaleHeight);
}
} else {
if(originHeight/(originWidth/scaleWidth) < scaleHeight) {
image.height(scaleHeight);
}else {
image.width(scaleWidth);
}
}
//判断是否需要居中
if(middle) {
var marginLeft = (originWidth/(originHeight/scaleHeight) - scaleWidth) / 2;
var marginTop = (originHeight/(originWidth/scaleWidth) - scaleHeight) / 2;
ratio > 1 ? image.css('marginLeft', -marginLeft) : image.css('marginTop', -marginTop);
}
}
```
## 实时统计input输入内容
```javascript
var el = document.getElementById('element')
el.addEventListener('compositionstart', onCompositionStart);
el.addEventListener('compositionend', onCompositionEnd);
function onCompositionStart (e) {
e.target.composing = true;
}
function onCompositionEnd (e) {
e.target.composing = false;
trigger(e.target, 'input');
}
function trigger (el, type) {
var e = document.createEvent('HTMLEvents');
e.initEvent(type, true, true);
el.dispatchEvent(e);
}
el.addEventListener('input', function(e){
if(!e.target.composing) {
console.log(this.value);
}
});
```
## WAP自动播放音乐
微信通过监听特定的事件可实现自动播放,UC浏览器不支持自动播放,IOS、安卓需要用户交互后才能播放音乐
```javascript
var music = document.getElementById('audio');
//微信
document.addEventListener("WeixinJSBridgeReady", function () {
music.play();
}, false);
//IOS、安卓
$(document).on('touchstart', function() {
music.load()
music.play();
})
```
## 展开收起
```
// 文本内容
export const ContentText = memo(({ text, maxLine = 3 }) => {
if (!text) {
return null;
}
const [showAll, setShowAll] = useState(false);
const [showControlBtn, setShowControlBtn] = useState(false);
const ctnRef = useRef();
useEffect(() => {
if (text && ctnRef.current) {
if (ctnRef.current.scrollHeight > ctnRef.current.offsetHeight) {
setShowControlBtn(true);
} else {
setShowControlBtn(false);
}
}
}, [text]);
// 展开
const onSpread = useCallback((e) => {
e.stopPropagation();
setShowAll(true);
}, []);
// 收起
const onClose = useCallback((e) => {
e.stopPropagation();
setShowAll(false);
}, []);
return (
<div>
<p
ref={ctnRef}
className={`content-text`}
style={!showAll ? { WebkitLineClamp: maxLine } : null}
dangerouslySetInnerHTML={{ __html: text }}
></p>
{showControlBtn ? (
<div>
{showAll ? (
<span onClick={onClose}>
收起
</span>
) : (
<span onClick={onSpread}>
展开
</span>
)}
</div>
): null}
</div>
);
});
```
```css
.content-text {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
word-break: break-all;
}
```
## 固定按钮
~~~ css
.fans-intro-distribution-btn {
position: absolute;
right: 0;
bottom: 270px;
display: flex;
align-items: center;
padding: 10px 20px 15px;
background-color: #fff7db;
border-radius: 100px 0px 0px 100px;
transform: translateX(0px);
transition: transform 0.3s linear 1s;
&.scroll {
transition: opacity,transform 0.5s linear;
}
}
~~~
~~~ javascript
import React, { useEffect, useState, useRef, useCallback } from 'react';
let timer = null;
export function DistributionBtn ({
scrollNode,
handleClick,
}) {
const [isScroll, setIsScroll] = useState(false);
const [leftValue, setleftValue] = useState(0);
const spanRef = useRef();
const btnRef = useRef();
// 监听滚动条
const handleScroll = useCallback(() => {
const node = typeof document != 'undefined' && document.querySelector(`.${scrollNode}`);
if (node) {
node.addEventListener('scroll', () => {
setIsScroll(true);
timer && clearTimeout(timer);
timer = setTimeout(()=>{
setIsScroll(false);
}, 400)
})
}
})
const setTranslateValue = useCallback(() => {
var paddingLeft = Number(window.getComputedStyle(btnRef.current)['padding-right'].replace(/px/, ''));
var spanWidth = spanRef.current.clientWidth;
setleftValue(paddingLeft + spanWidth);
}, [btnRef])
useEffect(() => {
if ( !scrollNode) return;
handleScroll();
setTranslateValue();
return () => {
timer && clearTimeout(timer);
}
}, [scrollNode]);
return (
<div
className={`fans-intro-distribution-btn ${isScroll ? 'scroll' : ''}`}
style={{
transform: isScroll ? [`translateX(${leftValue}px)`] : 'none',
}}
ref={btnRef}
onClick={handleClick}
>
<i className="red-packet"></i>
<span ref={spanRef}>分享赚0元</span>
<i className="distribution-bg"></i>
</div>
)
}
~~~
## 获取上传视频封面
~~~
// 选择视频
handleChooseVideo() {
//...
let file = this.$refs.chooseVideo.files[0]
if(!file) return
let size = Math.floor(file.size / 1024)
if(size > 3*1024) {
alert('请选择3MB以内的视频')
return false
}
// 获取视频地址
let videoUrl
if(window.createObjectURL != undefined) {
videoUrl = window.createObjectURL(file)
} else if (window.URL != undefined) {
videoUrl = window.URL.createObjectURL(file)
} else if (window.webkitURL != undefined) {
videoUrl = window.webkitURL.createObjectURL(file)
}
let $video = document.createElement('video')
$video.src = videoUrl
// 防止移动端封面黑屏或透明白屏
$video.play()
$video.muted = true
$video.addEventListener('timeupdate', () => {
if($video.currentTime > .1) {
$video.pause()
}
})
// 截取视频第一帧作为封面
$video.addEventListener('loadeddata', function() {
setTimeout(() => {
var canvas = document.createElement('canvas')
canvas.width = $video.videoWidth * .8
canvas.height = $video.videoHeight * .8
canvas.getContext('2d').drawImage($video, 0, 0, canvas.width, canvas.height)
let videoThumb = canvas.toDataURL('image/png')
console.log(videoThumb)
}, 16);
})
},
~~~
## 让一个scroll的容器跳转到底部
~~~
export const scrollToBottom = function (scrollContainer) {
const scrollY = scrollContainer.scrollHeight,
scrollX = scrollContainer.scrollLeft;
scrollContainer.scroll(scrollX, scrollY);
}
~~~
~~~
const inputRef = React.useRef(null);
// 初始化input
const initEl = React.useCallback((el) => {
if (el) {
inputRef.current = el;
el.focus();
}
}, []);
const handleFoucs = React.useCallback(() => {
if (inputRef.current) {
scrollToBottom(document.documentElement);
}
}, []);
<textarea
ref={initEl}
onFocus={handleFoucs}
/>
~~~
## iOS、安卓视频播放
~~~
<video
webkit-playsinline="true" // iOS10 设置视频在小窗内播放
playsinline="true" // iOS微信浏览器支持小窗内播放
x-webkit-airplay="allow"
x5-video-player-type="h5" // 启用H5播放器,wechat安卓版特性
x5-video-player-fullscreen="true" // 全屏设置
x5-video-orientatioon="portraint" // 播放器的方向,landscape横屏,portraint竖屏,默认值为竖屏
style="object-fit: fill"
/ >
~~~
## 打开原生应用
~~~
<a href="weixin://">打开微信</a>
<a href="alipays://">打开支付宝</a>
<a href="alipays://platformapi/startapp?saId=10000007">打开支付宝的扫一扫功能</a>
<a href="alipays://platformapi/startapp?appId=60000002">打开支付宝的蚂蚁森林</a>
~~~
这种方式叫做`URL Scheme`,是一种协议,一般用来访问`APP`或者`APP`中的某个功能/页面(如唤醒`APP`后打开指定页面或者使用某些功能)😒
`URL Scheme`的基本格式如下:
~~~
行为(应用的某个功能/页面)
|
scheme://[path][?query]
| |
应用标识 功能需要的参数
复制代码
~~~
一般是由`APP`开发者自己定义,比如规定一些`参数`或者`路径`让其他开发者来访问,就像上面的例子🍤
注意事项:
* 唤醒`APP`的条件是你的手机已经安装了该`APP`
* 某些浏览器会禁用此协议,比如微信内部浏览器(除非开了白名单)
## 解决input失焦后页面没有回弹

一般出现在`IOS设备中的微信内部浏览器`,出现的条件为:
* 页面高度过小
* 聚焦时,页面需要往上移动的时候
所以一般`input`在页面上方或者顶部都不会出现无法回弹
解决办法为,在聚焦时,获取当前滚动条高度,然后失焦时,赋值之前获取的高度:
~~~
<template>
<input type="text" @focus="focus" @blur="blur">
</template>
<script>
export default {
data() {
return {
scrollTop: 0
}
},
methods: {
focus() {
this.scrollTop = document.scrollingElement.scrollTop;
},
blur() {
document.scrollingElement.scrollTo(0, this.scrollTop);
}
}
}
</script>
~~~
## 滑动穿透
### 方法1
当在`遮罩`上滑动的时候,是会穿透到父节点的,最简单的办法就是阻住默认行为:
~~~
document.querySelector(".mask").addEventListener("touchmove", event => {
event.preventDefault();
});
~~~
如果在`vue`中,你可以这么写:
~~~
<div class="mask" @touchumove.prevent></div>
~~~
如果`.content`也有滚动条,那么只要阻止`遮罩`本身就行:
~~~
document.querySelector(".mask").addEventListener("touchmove", event => {
if (event.target.classList.contains("mask")) event.preventDefault();
});
~~~
或者:
~~~
<div class="mask" @touchumove.self.prevent></div>
~~~
### 方法2
[参考链接(已失效)](https://uedsky.com/2016-06/mobile-modal-scroll/)
~~~javascript
ModalHelper: (function(bodyCls) {
var scrollTop
return {
afterOpen: function() {
scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
$('body').addClass(bodyCls)
document.body.style.top = -scrollTop + 'px'
},
beforeClose: function() {
$('body').removeClass(bodyCls)
// scrollTop lost after set position:fixed, restore it back.
if (document.documentElement) {
document.documentElement.scrollTop = scrollTop
} else {
document.body.scrollTop = scrollTop
}
}
}
})('modal-open')
~~~
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码