cubic-bezier三次贝塞尔时间函数

time ratio output ratio

{{ 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)
    }
    }
    }
    京ICP备14036213号-2