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