11
This commit is contained in:
168
src/directives/custom-animation.js
Normal file
168
src/directives/custom-animation.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 自定义动画指令
|
||||
* v-custom-animation.scroll.once.parent="{GetRoot, activeClass}"
|
||||
* 修饰符
|
||||
* scroll: 是否监听滚动事件
|
||||
* once: 是否只执行一次
|
||||
* parent: 是否监听父元素滚动事件-优先级(GetRoot > parent > document)
|
||||
* 参数
|
||||
* GetRoot: 获取根元素函数-优先级(GetRoot > parent > document)
|
||||
* activeClass: 激活类名-默认值(active)
|
||||
*
|
||||
* 子元素动画
|
||||
* <div translate-x-s="-100" translate-x="100"></div>
|
||||
* 添加动画属性
|
||||
* translate-x-s: 水平方向移动开始位置
|
||||
* translate-x: 水平方向移动结束位置
|
||||
* ......(属性支持查看 T 对象)
|
||||
*
|
||||
*/
|
||||
const roots = new Map()
|
||||
const els = new Map()
|
||||
const T = {
|
||||
translateX_s: 'translate-x-s',
|
||||
translateX: 'translate-x',
|
||||
translateY_s: 'translate-y-s',
|
||||
translateY: 'translate-y',
|
||||
scale_s: 'scale-s',
|
||||
scale: 'scale',
|
||||
scaleX_s: 'scale-x-s',
|
||||
scaleX: 'scale-x',
|
||||
scaleY_s: 'scale-y-s',
|
||||
scaleY: 'scale-y',
|
||||
rotate_s: 'rotate-s',
|
||||
rotate: 'rotate',
|
||||
rotateX_s: 'rotate-x-s',
|
||||
rotateX: 'rotate-x',
|
||||
rotateY_s: 'rotate-y-s',
|
||||
rotateY: 'rotate-y',
|
||||
rotateZ_s: 'rotate-z-s',
|
||||
rotateZ: 'rotate-z',
|
||||
opacity_s: 'opacity-s',
|
||||
opacity: 'opacity',
|
||||
}
|
||||
const types = Object.values(T)
|
||||
const typesStr = types.map(v => `[${v}]`).join(',')
|
||||
const resize = new ResizeObserver((e) => {
|
||||
e.forEach(({ target }) => {
|
||||
requestAnimationFrame(() => handleScroll({ target }))
|
||||
})
|
||||
})
|
||||
|
||||
export default {
|
||||
name: 'custom-animation',
|
||||
mounted(el, binding) {
|
||||
const { value, modifiers } = binding
|
||||
const {
|
||||
GetRoot,// 获取根元素函数
|
||||
activeClass = 'active'// 激活类名
|
||||
} = value || {}
|
||||
const {
|
||||
scroll = false,// 是否监听滚动事件
|
||||
once = false,// 是否只执行一次
|
||||
parent = false,// 是否监听父元素滚动事件
|
||||
} = modifiers
|
||||
const root = GetRoot ? GetRoot() : parent ? el.parentElement : document;
|
||||
if (el === root) return;
|
||||
add(el, root)
|
||||
els.set(el, {
|
||||
root,// 根元素
|
||||
scroll,
|
||||
once,
|
||||
activeClass,
|
||||
isActive: false,
|
||||
})
|
||||
},
|
||||
beforeUnmount(el, binding) {
|
||||
remove(el)
|
||||
els.delete(el)
|
||||
}
|
||||
};
|
||||
function add(el, root = document) {
|
||||
requestAnimationFrame(() => handleScroll({ target: root }))
|
||||
resize.observe(el)
|
||||
if (roots.has(root)) {
|
||||
let obj = roots.get(root)
|
||||
obj.els.push(el)
|
||||
obj.observer.observe(el)
|
||||
return
|
||||
}
|
||||
resize.observe(root)
|
||||
root.addEventListener('scroll', handleScroll)
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const { target } = entry
|
||||
const obj = els.get(target)
|
||||
if (!obj) return
|
||||
if (obj.once && obj.isActive) return;// 只执行一次,且已可见,不执行
|
||||
obj.isActive = entry.isIntersecting;
|
||||
target.classList.toggle(obj.activeClass, obj.isActive)
|
||||
})
|
||||
}, { root })
|
||||
observer.observe(el)
|
||||
roots.set(root, { els: [el], observer, resize })
|
||||
}
|
||||
function remove(el, root = document) {
|
||||
if (!roots.has(root)) return
|
||||
const obj = roots.get(root)
|
||||
if (obj.els.includes(el)) {
|
||||
obj.observer.unobserve(el)
|
||||
obj.resize.unobserve(el)
|
||||
obj.els.splice(obj.els.indexOf(el), 1)
|
||||
}
|
||||
if (obj.els.length === 0) {
|
||||
obj.observer.disconnect()
|
||||
resize.unobserve(root)
|
||||
root.removeEventListener('scroll', handleScroll)
|
||||
roots.delete(root)
|
||||
}
|
||||
}
|
||||
|
||||
var timer = null
|
||||
async function handleScroll({ target: root }) {
|
||||
clearTimeout(timer)
|
||||
timer = await new Promise(resolve => setTimeout(resolve, 10))
|
||||
const obj = roots.get(root)
|
||||
if (!obj) return
|
||||
obj.els.forEach((el) => {
|
||||
const item = els.get(el)
|
||||
if (!item) return
|
||||
if (!item.scroll) return
|
||||
const children = el.querySelectorAll(typesStr)
|
||||
if (children.length === 0) return
|
||||
const elTop_bottom = root.offsetHeight - (el.offsetTop - root.offsetTop - root.scrollTop)
|
||||
const maxHeight = root.offsetHeight + el.offsetHeight
|
||||
const p = Math.min(1, Math.max(0, elTop_bottom / maxHeight))
|
||||
children.forEach((item) => {
|
||||
item.style.transition = 'transform 0.5s ease-out'
|
||||
|
||||
const tX = getCurrentValue(item, T.translateX_s, T.translateX, p)
|
||||
const tY = getCurrentValue(item, T.translateY_s, T.translateY, p)
|
||||
const sx = getCurrentValue(item, T.scaleX_s, T.scaleX, p, T.scale_s, T.scale, 1)
|
||||
const sy = getCurrentValue(item, T.scaleY_s, T.scaleY, p, T.scale_s, T.scale, 1)
|
||||
const r = getCurrentValue(item, T.rotate_s, T.rotate, p)
|
||||
const rx = getCurrentValue(item, T.rotateX_s, T.rotateX, p)
|
||||
const ry = getCurrentValue(item, T.rotateY_s, T.rotateY, p)
|
||||
const rz = getCurrentValue(item, T.rotateZ_s, T.rotateZ, p)
|
||||
const transform = `translate(${tX}px, ${tY}px) scale(${sx}, ${sy}) rotate(${r}deg) rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`
|
||||
item.style.transform = transform
|
||||
if (hasAttr(item, [T.opacity_s, T.opacity])) {
|
||||
item.style.opacity = getCurrentValue(item, T.opacity_s, T.opacity, p, T.opacity_s, T.opacity, 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
function getCurrentValue(el, start, end, progress, bStart, bEnd, defaultValue = 0) {
|
||||
// const startNum = Number(el.getAttribute(start) || el.getAttribute(bStart)) || defaultValue
|
||||
// const endNum = Number(el.getAttribute(end) || el.getAttribute(bEnd)) || defaultValue
|
||||
const startNum = hasAttr(el, start) ? Number(el.getAttribute(start)) : hasAttr(el, bStart) ? Number(el.getAttribute(bStart)) : defaultValue
|
||||
const endNum = hasAttr(el, end) ? Number(el.getAttribute(end)) : hasAttr(el, bEnd) ? Number(el.getAttribute(bEnd)) : defaultValue
|
||||
return startNum + (endNum - startNum) * progress
|
||||
}
|
||||
function hasAttr(el, attr) {
|
||||
if (Array.isArray(attr)) {
|
||||
return attr.some(v => el.hasAttribute(v))
|
||||
} else {
|
||||
return el.hasAttribute(attr)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user