hytstart.github.io's Introduction
hytstart.github.io's People
Forkers
lumengweihytstart.github.io's Issues
年终活动h5动画总结
年终活动h5动画总结
css3 + react-id-swiper + react + redux + saga
采用postcss的Autoprefixer插件,即可满足大多数oppo,vivo手机的兼容性问题。
1. 动画相关主要内容:
- 流星
- 闪烁星星
- 字的晃动
- 人的移动(动画 + 监听动画结束时间)
- 桥的铺垫
- 开启旅程按钮缩放
- 旋转(+ 兼容)
- react-id-swiper
- swiper配合css3实现切换
2. css3动画语法
- animation
animation-name
animation-duration
animation-timing-function 速度曲线
animation-delay
animation-iteration-count
animation-direction
play-state
fill-mode
- transform
transform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。
其中 transform-origin (属性改变被转换元素的中心)。
- transition
三者区别: animation 动画,关键帧,往复性。 transition 过渡, 属性,触发动作,一过性。 transform 变换, 复杂的变换参数。
3. 示例 https://github.com/hytStart/React_chouchou/blob/master/src/components/home/ui/action/action.scss
- 流星
改变位置
translate3d
,透明度opacity
和大小scale
。
流星尾巴采用伪元素元素:after
旋转-45deg
(旋转基点为左transform-origin: left;
);采用border
可以实现,靠近头部越亮,靠近尾部越暗。
.star {
display: block;
width: 5px;
height: 5px;
border-radius: 50%;
background: #FFF;
top: 10px;
left: 200px;
position: relative;
animation: star-ani 6s infinite ease-out;
box-shadow: 0 0 5px 5px rgba(255, 255, 255, .3);
opacity: 1;
}
.star:after {
content: '';
display: block;
top: 0px;
left: 40%;
border: 0px solid #fff;
border-width: 0px 90px 2px 90px;
border-color: transparent transparent transparent rgba(255, 255, 255, .3);
transform: rotate(-45deg) translate3d(1px, 3px, 0);
box-shadow: 0 0 1px 0 rgba(255, 255, 255, .1);
transform-origin: left;
}
@keyframes star-ani {
0% {
opacity: 0;
transform: scale(0) rotate(0) translate3d(0, 0, 0);
}
50% {
opacity: 1;
transform: scale(1) rotate(0) translate3d(-100px, 100px, 0);
}
100% {
opacity: 0;
transform: scale(1) rotate(0) translate3d(-200px, 200px, 0);
}
}
- 闪烁星星
改变透明度
.shine {
background: url('../../../../images/action/icon-star1.png') no-repeat center;
background-size: 100%;
width: 30px;
height: 40px;
position: absolute;
top: 90px;
left: 100px;
opacity: 0;
animation: opacity-change 0.5s ease-in-out infinite;
}
@keyframes opacity-change {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
- 字
分为两段动画,下降和上升。
translateY
改变即可。
.text-item-0 {
position: absolute;
width: 25px;
height: 75px;
top: 60px;
left: 100px;
background: url('../../../../images/action/S-start.png') no-repeat center;
background-size: 100%;
animation: letter-0 1.5s ease-in-out both, letter-0-1 2.0s ease-in-out 1.5s both;
}
@keyframes letter-0 {
0% {
transform: translateY(0)
}
50% {
transform: translateY(80px)
}
100% {
transform: translateY(0px)
}
}
@keyframes letter-0-1 {
0% {
opacity: 1;
}
100% {
top: -80px;
opacity: 0;
}
}
- 人移动
切换dom,添加类控制移动和暂停,以及切换背景人物。监听
animationend
事件。
jsx
{
peopleMove ?
<div
className={`${style.people_move} ${!pausedState && style.people_paused}`}
ref={start2 => { this.start2 = start2 }}
/>
:
<div className={style.people} />
}
css
.people {
width: 20px;
height: 64px;
position: absolute;
left: 10px;
top: 130px;
background: url('../../../../images/action/people.png') no-repeat center;
background-size: 100%;
opacity: 0;
animation: peopleUp 1s ease-in-out 0.5s both;
}
.people_move {
background: url('../../../../images/action/people_moveleft.gif');
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
width: 20px;
height: 64px;
position: absolute;
left: 10px;
top: 130px;
opacity: 1;
animation: PeopleMove 1.5s linear 0s both;
}
.people_paused {
width: 20px;
height: 64px;
background: url('../../../../images/action/people.png');
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
animation-play-state: paused;
}
@keyframes peopleUp {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes PeopleMove {
0% {
left: 10px;
top: 130px;
}
100% {
top: 20px;
left: 180px;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
}
// 监听动画结束时机
componentDidUpdate() {
const { peopleMove } = this.state
if (peopleMove) {
this.start2.addEventListener('animationend', this.end)
this.start2.addEventListener('webkitAnimationEnd', this.end)
this.start2.addEventListener('mozAnimationEnd', this.end)
this.start2.addEventListener('oAnimationEnd', this.end)
}
}
- 桥的出现
配合
background-size: cover;
属性实现。
.brige {
width: 0px;
background: url('../../../../images/action/bridge.png');
background-size: cover;
background-repeat: no-repeat;
height: 100px;
position: absolute;
top: 40px;
left: 30px;
animation: BridgeFadeIn 3s linear both;
opacity: 0;
}
@keyframes BridgeFadeIn {
0% {
width: 0px;
opacity: 0;
}
100% {
width: 200px;
opacity: 1;
}
}
- 渐变大(开始旅程)
利用
transform scale
2D 缩放转换。
.icon-ciecle {
display: block;
position: absolute;
left: 100px;
top: 80px;
width: 30px;
height: 30px;
background: url('../../../../images/action/icon-light.png') no-repeat center;
background-size: 100%;
animation: warn 1.2s ease-in-out 0s infinite both;
}
@keyframes warn {
0% {
transform: scale(0.1);
opacity: 0.0;
}
25% {
transform: scale(0.2);
opacity: 0.3;
}
50% {
transform: scale(0.4);
opacity: 0.5;
}
75% {
transform: scale(0.6);
opacity: 0.7;
}
100% {
transform: scale(0.8);
opacity: 0.0;
}
}
- 旋转
ios的
animation-play-state: paused;
不起作用,且animation
动画不可写在新增类里,必须写在一个类里。(测试中发现,这里有疑问。)
.music_img {
width: 40px;
height: 40px;
display: block;
position: absolute;
left: 100px;
top: 80px;
animation: rotating 3s linear infinite;
animation-play-state: running;
}
.rotate-pause {
animation-play-state: paused;
}
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
- react-id-swiper
Swiper的react版本,基本api都支持,具实验目测相当于Swiper v4。
- 不一定通过api。也可以通过重写css实现。
- 重写css切换效果的话,会影响它本身的动画效果,所以可通过添加不可滑动来控制。
1.
.swiper-wrapper {
transition-delay: 1.6s;
}
2.
noSwiping: true,
noSwipingClass: 'stop-swiping',
- Swiper实现的迷宫切换
1.swiper的
effect
属性与控制背景图片的opacity
,利用时间差实现最终效果。包括迷宫的切换,背包男的出现与消失,弹窗的出现与消失。
2.swiper属性的activeIndex
,可以得到滑动到第几页,通过改变dom
或者改变类,控制第几页动画的发生。
// 根据activeIndex拿到的页数,控制该页数state的改变,通过切换dom和添加类的方式,达到进场与退场的动画效果。
// 添加类stop-swiping控制不可滑动,动画完成后,才可继续滑动下一页。
const mazeStyle = classNames({
[styles['maze-1']]: true,
[styles['maze-out']]: firstMazeOut,
})
const peopleStyle = classNames({
[styles['people-1']]: true,
[styles['people-out']]: firstMazeOut,
})
const popCardStyle = classNames({
[styles['pop-card-container']]: true,
[styles['pop-card-out']]: firstMazeOut,
})
{
firstMazeIn ?
<div className={!isSlideFirst && "stop-swiping"}>
<div className={styles['maze-container']}>
<div className={mazeStyle} />
<div className={peopleStyle} />
</div>
<div className={popCardStyle}>
<ModalScene {...this.props} />
</div>
</div>
:
<div /> // 不添加会影响swiper自身页数的判断
}
4. 动画总结
别人写的真牛X,自己只会
opacity
,translate
。
5. 待完善
- html2canvas
- 资源加载
- jsbridge使用与注意事项
rudux saga ( take 、 takeEvery )
rudux saga中的take 与 takeEvery
taro,作为react开发者,使用注意事项
继去年毕设,使用小程序原生语言开发了一个英语学习小程序(smart英语学习》》)以后,一年没有写过小程序了。最近心血来潮,准备用很火的Taro(类似于react语法,多端)开发一个课堂签到小程序,踩踩坑,感受一下。
本文概要
- 使用Taro,redux开发的学生课堂签到小程序展示
- *Taro的基本使用
- *作为React开发者,使用的注意事项(仅包括实践中遇到的)
- 实际开发中的处理
- Taro以及微信小程序开发中遇到的问题总结
- TODO
一、签到小程序
功能概述:基于地理位置(经纬度)的签到平台。另外包括校内新闻模块,word资料下载模块,个人信息模块等。扫码登录: 学生体验账号 123456,密码 123456。
1. 基于腾讯api经纬度的签到
2. 新闻部分,word资料下载
3. 个人信息模块
二、Taro的基本使用
yarn global add @tarojs/cli
,使用 yarn 安装 CLItaro init myApp
创建模板项目(按照一系列默认命令,即可创建redux ,mobx等生成器模板)。并且之后会默认安装依赖。npm un dev:weap
,进入微信开发工具查看自己的小程序。- 具体组件和api和小程序相似,可查看Taro文档自行了解。
- 之后开发就类似于React了。注意
componentDidShow、 componentDidMount
生命周期在小程序中的不同表现。tab页componentDidMount
只走一次。
备注:Taro 默认对小程序的异步 API 进行了包装,可以像使用 Promise 那样进行调用。
// WX
wx.request({
url: '', // 仅为示例,并非真实的接口地址
data: {},
header: {
'content-type': 'application/json' // 默认值
},
success(res) {}
})
// Taro
Taro.request(url).then(function (res) {
console.log(res)
})
三、作为React开发者,使用的注意事项(仅包括实践中遇到的)
-
sourcemap
不能用就很xxxxx -
不能解构传值,需要
key value
传给子组件 -
不能在
render
之外写jsx
-
this.props
传来的函数必须on
或者dispatch
开头 -
父组件传来的
props
,必须定义在static defaultProps
里,要不然获取不到 -
componentDidMount
,在微信/百度/字节跳动/支付宝小程序中这一生命周期方法对应 app 的onLaunch
-
componentDidShow
在微信/百度/字节跳动/支付宝小程序中这一生命周期方法对应onShow
-
componentDidHide
在微信/百度/字节跳动/支付宝小程序中这一生命周期方法对应onHide
-
JS 代码里必须书写单引号,特别是 JSX 中,如果出现双引号,可能会导致编译错误
-
环境变量
process.env
的使用,不要以解构的方式来获取通过env
配置的process.env
环境变量,请直接以完整书写的方式process.env.NODE_ENV
来进行使用 -
使用
this.$componentType
来判断当前Taro.Component
是页面还是组件,可能取值分别为PAGE
和COMPONENT
-
不支持无状态组件
-
不能在包含 JSX 元素的
map
循环中使用if
表达式 -
不能使用
Array#map
之外的方法操作 JSX 数组 -
父组件要往子组件传递函数,属性名必须以
on
开头
以上是使用过程中遇到的问题,更多注意事项请查阅官方文档
四、实际开发中的处理
1. alias同样可以使用。
// config/index.js
alias: {
'@actions': path.resolve(__dirname, '..', 'src/actions'),
'@assets': path.resolve(__dirname, '..', 'src/assets'),
'@components': path.resolve(__dirname, '..', 'src/components'),
'@constants': path.resolve(__dirname, '..', 'src/constants'),
'@reducers': path.resolve(__dirname, '..', 'src/reducers'),
'@style': path.resolve(__dirname, '..', 'src/style'),
'@util': path.resolve(__dirname, '..', 'src/util')
},
2. Taro.requset()的简单处理
// feth.js
import Taro from '@tarojs/taro'
const defaultMethod = 'POST'
const successStatus = 200
const successFlag = 1
const messageToast = title => {
Taro.showToast({
title,
icon: 'none',
duration: 1000
})
}
export default function fetch(options) {
const {
url,
method = defaultMethod,
params,
showErrorToast = true,
} = options
return Taro.request({
url,
method,
data: params,
}).then(response => {
const { statusCode, data } = response
// 不是200以外的
if (statusCode != successStatus) {
const { error } = data
// reject出去。showToast在catch里执行。
const errMessage = {errMsg: error || '请求接口失败'}
return Promise.reject(errMessage)
} else {
// flag是不是1的判断
const { flag, message, data: datas } = data
if (flag == successFlag) {
return Promise.resolve(datas)
} else {
const errMessage = {errMsg: message || '流程错误'}
return Promise.reject(errMessage)
}
}
}).catch(errors => {
const { errMsg } = errors
if (showErrorToast) {
messageToast(errMsg || '发起请求异常')
}
const errMessage = errMsg || '发起请求异常'
return Promise.reject(errMessage)
})
}
3. 单击按钮,滚动到相应节点
toggleComments = () => {
Taro.createSelectorQuery().select('#comments-id').boundingClientRect(function(rect){
// 使页面滚动到响应位置
Taro.pageScrollTo({
scrollTop: rect.bottom
})
}).exec()
}
五、Taro以及微信小程序开发中遇到的问题总结
1. doc文档的下载与预览
小程序目前提供了wx.downloadFile的API,但目前只支持长传和下载图片,语音,视频 三种类型的文件。doc文件会下载为临时路径的 .myword文件,可供预览(安卓手机默认微信内置浏览器打开,可转发至电脑。ios tbd)。
Taro.showLoading()
const params = {
url,
fileType: "doc",
success(res) {
// 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容
if (res.statusCode === 200) {
const wxPath = res.tempFilePath
Taro.openDocument({
filePath: wxPath,
fileType: "doc",
success: function (ress) {
Taro.hideLoading()
console.log(ress)
},
fail: function (resfo) {
Taro.hideLoading()
util.showToast('打开文档失败')
}
})
}
},
fail: function (resfd) {
Taro.hideLoading()
util.showToast('文件下载失败')
},
}
Taro.downloadFile(params).then()
2. 获取地理位置,经纬度,用于签到
wx.getLocation({
type: 'gcj02', // wgs84 返回 gps 坐标,gcj02 返回可用于 wx.openLocation 的坐标
success(res) {
const { latitude, longitude } = res
}
})
①、wx.getLocation,调用前需要 用户授权。scope.userLocation
②、发起签到与扫码签到时,应保证type
相同。
// 用户授权,app.js中配置项
permission: {
"scope.userLocation": {
"desc": "获取用户的签到位置"
}
},
3. 不能长按识别(除小程序码外的)二维码 (tbd)
预览二维码图片以后,不支持识别二维码。
getEventData(data, tag) {
return data.currentTarget.dataset[tag];
},
previewImage = (e) => {
const current = getEventData(e, 'src')
Taro.previewImage({
current: current, // 当前显示图片的http链接
urls: [current] // 需要预览的图片http链接列表
})
}
六、TODO
- 发挥多端的优势,尝试其他小程序,h5等的打包发布。
- 继续跟进word文档下载,更换二维码为小程序二维码带参数(
wxacode.getUnlimited
) - openid对于多端的影响
尾语
==菜的抠脚,大佬轻喷。内容较粗糙,有问题请指正。==
有必要夸一下,小程序的处理速度还是很快的,两个小时审核就通过了。
手机百度APP,H5网页position fixed属性失效
问题描述
- 手机百度APP,投放的H5网页
position:fixed
属性失效。 - h5底部有个吸底的
div
(类似于底部下载app),里面有background-img
。百度app一进页面就被关闭了。原因是,百度认为我们是在推送广告,被他们检测到了。
解决办法
-
下午同事反馈给信息,说是宣传部门自己做的h5宣传页有问题。kuangkuang扔给我一个zip包,打开一看,
index.html,js,images
。赶紧放在nginx下用手机访问试了试,看了看别人的代码。会不会是需要主动触发,会不会是需要基于top
定位等等,胡乱试了半天,都不好使。 -
结果,是因为,底部的
div
高度小于150px
的时候,百度就会认为是弹窗广告,进而关闭。 -
fixed
的div
高度大于150px
就可以了,改了下别人代码,告诉他为啥,让她自己去弄吧。
.footer {
display: none;
position: fixed;
bottom: -8px;
left: 0px;
right: 0px;
width: 375px;
height: 150px;
background: url("./images/xd-img.png") no-repeat;
background-size: 375px 136px;
background-position: bottom;
}
对,大于等于150px就好使了!!!!
到最后,发现他们的移动端是基于px的,不用适配机型的be strong
对我来说,博客首先是一种知识管理工具,其次才是传播工具。我的技术文章,主要用来整理我还不懂的知识。我只写那些我还没有完全掌握的东西,那些我精通的东西,往往没有动力写。炫耀从来不是我的动机,好奇才是。
js拖拽div(有子元素)
###由于div内子元素的影响,offsetY等都不可控,使用基于clientY的方式
使用到getBoundingClientRect()
返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合。
DOMRect对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的
// 基于left , bottom
function drag(element) {
function getEvent(event) {
return event || window.event
}
var position = {
state: 0,
startX: 0,
startY: 0,
left: 0,
bottom: 0,
endX: 0,
endY: 0,
}
element.addEventListener('mousedown', function(event) {
var e = getEvent(event)
position.startX = e.clientX
position.startY = e.clientY
position.left = element.getBoundingClientRect().left
position.bottom = document.body.clientHeight - element.getBoundingClientRect().bottom
position.state = 1
}, false)
document.addEventListener('mouseup', function(event) {
position.state = 0
}, false)
document.addEventListener('mousemove', function(event) {
var e = getEvent(event)
if (position.state) {
element.style.userSelect = 'none'
element.style.cursor = 'move'
position.endX = e.clientX
position.endY = e.clientY
element.style.left = position.left + position.endX - position.startX + 'px'
element.style.bottom = position.bottom + position.startY - position.endY + 'px'
}
}, false)
}
drag(node)
###没有获取原始left,bottom,但是因为子元素的影响。会有偏差
//基于left , bottom
function drag(element) {
var position = {
layerX: 0,
layerY: 0,
state: 0
};
//获得兼容的event对象
function getEvent(event) {
return event || window.event
}
element.addEventListener('mousedown', function(event) {
var e = getEvent(event)
position.layerX = e.layerX
position.layerY = e.layerY
position.state = 1
}, false)
document.addEventListener('mousemove', function(event) {
var e = getEvent(event)
if (position.state) {
var clientHeight = document.body.clientHeight
var elementHeight = element.offsetHeight // (包括padding、border、水平滚动条)
position.endX = e.pageX
position.endY = e.pageY
//设置绝对位置在文档中,鼠标当前位置-开始拖拽时的偏移位置
element.style.left = position.endX - position.layerX + 'px'
// bottom = 网页的可视区域height - 停下的Y - (元素高度 - 元素内部偏移)
element.style.bottom = clientHeight - position.endY - (elementHeight - position.layerY) + 'px'
}
}, false)
document.addEventListener('mouseup', function(event) {
position.state = 0
}, false)
}
-
pageX和pageY获取的是鼠标指针距离文档(HTML)的左上角距离,不会随着滚动条滚动而改变。
-
clientX和clientY获取的是鼠标指针距离可视窗口(不包括上面的地址栏和滑动条)的距离,会随着滚动条滚动而改变
-
如果是fix定位的元素,其实取clientX是不变的。但是pageX会变
-
screenX/screenY 鼠标在屏幕上的坐标。screenX,screenY的最大值不会超过屏幕分辨率
clientX 设置或获取鼠标指针位置相对于当前窗口的 x 坐标,其中客户区域不包括窗口自身的控件和滚动条。
clientY 设置或获取鼠标指针位置相对于当前窗口的 y 坐标,其中客户区域不包括窗口自身的控件和滚动条。
offsetX 设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标。 (受子元素影响)
offsetY 设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标。 (受子元素影响)
screenX 设置或获取获取鼠标指针位置相对于用户屏幕的 x 坐标。
screenY 设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。
5个和元素高度、滚动、位置相关的属性
clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度
offsetHeight:包括padding、border、水平滚动条,但不包括margin的元素的高度
overflow=scroll时候,
offsetTop 当前元素顶部距离最近父元素顶部的距离,和有没有滚动条没有关系,是相对于离它最近的具有绝对或相对定位的父级元素的距离
<style>
* {
margin: 0;
padding: 0;
}
.ddd {
width: 200px;
height: 200px;
background-color: pink;
position: relative;
padding: 10px;
}
.ppp {
width: 50px;
height: 50px;
background-color: blue;
}
</style>
<div class="ddd">
<p class="ppp"></p>
</div>
document.getElementsByClassName('ppp')[0].addEventListener('mousedown', function(e) {
console.log('offsetTop', document.getElementsByClassName('ppp')[0].offsetTop) // 10
console.log('e.offsetTop', e.offsetTop) // undefined
console.log('offsetY', e.offsetY) // 22
}, false)
document.getElementsByClassName('ddd')[0].addEventListener('mousedown', function(e) {
console.log('offsetY', e.offsetY) // 相对于自己的Y,点div 30的话, 点相同高度的子元素,则是 20
}, false) // 这里改变false, true ,点击p标签,会有影响。如果true, div事件限制性,如果false,p事件先执行
addEventListener true 捕获截断 false冒泡截断
防抖,节流
防抖 :
const deb = (fn, delay, immediate) => {
let timer = null
return function() {
const context = this
timer && clearTimeout(timer)
if (immediate) {
!timer && fn.apply(context, arguments)
}
timer = setTimeout(() => {
fn.apply(context, arguments)
}, delay)
}
}
节流
const throttle = (fn, delay = 2000) => {
let timer = null
let startTime = new Date()
return function() {
const context = this
let currentTime = new Date()
clearTimeout(timer)
if (currentTime - startTime >= delay) {
fn.apply(context, arguments)
startTime = currentTime
} else {
//让方法在脱离事件后也能执行一次
timer = setTimeout(() => {
fn.apply(context, arguments)
}, delay)
}
}
}
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.