{{ cubicBezierStr }}
用在CSS的transition或者animation动画中的时间函数timing-function,描述在过渡或动画中一维数值的速度改变,通常称为缓动函数。
三次/立方贝塞尔曲线(cubic Bézier curves)是CSS时间函数常用的一种。语法为:
cubic-bezier(x1, y1, x2, y2)
横轴为时间比例(time ratio),纵轴为完成状态(output ratio)
曲线由四个点来定义,P0、P1、P2和P3
P0(0, 0)和P3(1, 1)是固定在坐标系上的起点和终点
语法中x1, y1, x2, y2表示两个过渡点P1和P2的横纵坐标
x1和x2是时间值,必须在[0, 1]范围内
y1和y2可以是负数或者大于1,从而实现弹跳动画效果
y值超过实际允许范围(比如颜色值大于255或小于0)会被修改为允许范围内的最接近值
常用的命名过渡效果,等同于某些数值的cubic-bezier。
名称 | 过渡效果 | 等同于 |
---|---|---|
linear | 以相同速度开始至结束 | cubic-bezier(0, 0, 1, 1) |
ease | 慢速开始,然后变快,然后慢速结束 | cubic-bezier(.25, .1, .25, 1) |
ease-in | 慢速开始 | cubic-bezier(.42, 0, 1, 1) |
ease-out | 慢速结束 | cubic-bezier(0, 0, .58, 1) |
ease-in-out | 慢速开始和结束 | cubic-bezier(.42, 0, .58, 1) |
本页面用Vue实现响应式SVG来简单模拟cubic-bezier三次贝塞尔时间函数。
SVG画布和cubic-bezier过渡点的坐标系不同需要分别定义。
data: { // SVG画布中的坐标 x1: 50, y1: 180, x2: 50, y2: 0,
// cubic-bezier过渡点的坐标 cx1: 0.25, cy1: 0.1, cx2: 0.25, cy2: 1,}
坐标转换,将时间值约束在[0, 1]范围内。
computed: { cubicBezierStr() { const f = n => { let r = String(n.toFixed(2)) r = r.replace(/^0+|0+$/g, '') r = r.replace(/\.$/, '') if (r === '') r = 0 return r } const { x1, y1, x2, y2 } = this const cx1 = x1 / 200 const cy1 = (200 - y1) / 200 const cx2 = x2 / 200 const cy2 = (200 - y2) / 200 return `cubic-bezier(${f(cx1)}, ${f(cy1)}, ${f(cx2)}, ${f(cy2)})` }}
实现过渡点的拖动与计算,兼容桌面和移动设备mousemove/touchmove。
methods: { handleStart(event, point) { event.preventDefault() const isTouch = !!event.touches if (isTouch && event.touches.length > 1) return if (isTouch) event = event.touches[0]
const { x1, y1, x2, y2 } = this this.dragState = { dragging: true, left: event.clientX, top: event.clientY }
document.onselectstart = () => false document.ondragstart = () => false
const handleMove = event => { event.preventDefault() if (isTouch) event = event.touches[0]
const constrain = n => Math.min(Math.max(0, n), 200) const deltaLeft = event.clientX - this.dragState.left const deltaTop = event.clientY - this.dragState.top if (point === 1) { this.x1 = constrain(x1 + deltaLeft) this.y1 = y1 + deltaTop } else if (point === 2) { this.x2 = constrain(x2 + deltaLeft) this.y2 = y2 + deltaTop } }
const handleEnd = () => { this.dragState.dragging = false if (isTouch) { document.removeEventListener('touchmove', handleMove) document.removeEventListener('touchend', handleEnd) } else { document.removeEventListener('mousemove', handleMove) document.removeEventListener('mouseup', handleEnd) } document.onselectstart = null document.ondragstart = null }
if (isTouch) { document.addEventListener('touchmove', handleMove, { passive: false }) document.addEventListener('touchend', handleEnd) } else { document.addEventListener('mousemove', handleMove) document.addEventListener('mouseup', handleEnd) } }}