炫酷提交操作按钮动画(附源代码)

[复制链接]
七夏(UID:1) 发表于 2024-10-8 11:49:15 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

图片

这是一个交互式的提交按钮,包含状态过渡的动画效果和庆祝的彩色纸屑效果。这个效果将实用的表单提交功能与吸引人的视觉反馈相结合,提供了良好的用户体验。

主要效果和实现原理如下:

  1. 按钮状态变化:

    • 初始状态显示"提交"
    • 点击后进入加载状态,显示加载动画
    • 加载完成后显示"成功",并触发彩色纸屑效果
  2. 动画效果:

  • 使用CSS动画实现按钮状态变化的过渡效果
  • 利用JavaScript控制按钮类名来触发不同的CSS动画
  1. 彩色纸屑效果:
  • 使用Canvas绘制彩色纸屑
  • 通过JavaScript模拟物理运动(重力、阻力等)
  • 使用requestAnimationFrame实现流畅的动画效果
  1. 实现原理:
  • HTML结构定义按钮和Canvas元素
  • CSS样式定义按钮外观和动画
  • JavaScript实现: a. 按钮状态管理和动画触发 b. 纸屑的生成、更新和渲染 c. 物理模拟(速度、位置计算) d. Canvas绘制和动画循环

使用方式

复制源代码到空白的html格式文件,在浏览中打开运行即可。

源代码

可上下滑动查看完整源代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <style>
    @keyframes loading {
      0% {
        cy: 10;
      }

      25% {
        cy: 3;
      }

      50% {
        cy: 10;
      }
    }

    body {
      -webkit-font-smoothing: antialiased;
      background-color: #f4f7ff;
    }

    canvas {
      height: 100vh;
      pointer-events: none;
      position: fixed;
      width: 100%;
      z-index: 2;
    }

    button {
      background: none;
      border: none;
      color: #f4f7ff;
      cursor: pointer;
      font-family: "Quicksand", sans-serif;
      font-size: 14px;
      font-weight: 500;
      height: 40px;
      left: 50%;
      outline: none;
      overflow: hidden;
      padding: 0 10px;
      position: fixed;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 190px;
      -webkit-tap-highlight-color: transparent;
      z-index: 1;
    }

    button::before {
      background: #1f2335;
      border-radius: 50px;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4) inset;
      content: "";
      display: block;
      height: 100%;
      margin: 0 auto;
      position: relative;
      transition: width 0.2s cubic-bezier(0.39, 1.86, 0.64, 1) 0.3s;
      width: 100%;
    }

    button.ready .submitMessage svg {
      opacity: 1;
      top: 1px;
      transition: top 0.4s ease 600ms, opacity 0.3s linear 600ms;
    }

    button.ready .submitMessage .button-text span {
      top: 0;
      opacity: 1;
      transition: all 0.2s ease calc(var(--dr) + 600ms);
    }

    button.loading::before {
      transition: width 0.3s ease;
      width: 80%;
    }

    button.loading .loadingMessage {
      opacity: 1;
    }

    button.loading .loadingCircle {
      animation-duration: 1s;
      animation-iteration-count: infinite;
      animation-name: loading;
      cy: 10;
    }

    button.complete .submitMessage svg {
      top: -30px;
      transition: none;
    }

    button.complete .submitMessage .button-text span {
      top: -8px;
      transition: none;
    }

    button.complete .loadingMessage {
      top: 80px;
    }

    button.complete .successMessage .button-text span {
      left: 0;
      opacity: 1;
      transition: all 0.2s ease calc(var(--d) + 1000ms);
    }

    button.complete .successMessage svg {
      stroke-dashoffset: 0;
      transition: stroke-dashoffset 0.3s ease-in-out 1.4s;
    }

    .button-text span {
      opacity: 0;
      position: relative;
    }

    .message {
      left: 50%;
      position: absolute;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 100%;
    }

    .message svg {
      display: inline-block;
      fill: none;
      margin-right: 5px;
      stroke-linecap: round;
      stroke-linejoin: round;
      stroke-width: 2;
    }

    .submitMessage .button-text span {
      top: 8px;
      transition: all 0.2s ease var(--d);
    }

    .submitMessage svg {
      color: #5c86ff;
      margin-left: -1px;
      opacity: 0;
      position: relative;
      top: 30px;
      transition: top 0.4s ease, opacity 0.3s linear;
      width: 14px;
    }

    .loadingMessage {
      opacity: 0;
      transition: opacity 0.3s linear 0.3s, top 0.4s cubic-bezier(0.22, 0, 0.41, -0.57);
    }

    .loadingMessage svg {
      fill: #5c86ff;
      margin: 0;
      width: 22px;
    }

    .successMessage .button-text span {
      left: 5px;
      transition: all 0.2s ease var(--dr);
    }

    .successMessage svg {
      color: #5cffa1;
      stroke-dasharray: 20;
      stroke-dashoffset: 20;
      transition: stroke-dashoffset 0.3s ease-in-out;
      width: 14px;
    }

    .loadingCircle:nth-child(2) {
      animation-delay: 0.1s;
    }

    .loadingCircle:nth-child(3) {
      animation-delay: 0.2s;
    }
  </style>
</head>

<body>
  <button id="button" class="ready" onclick="clickButton();">

  <div class="message submitMessage">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12.2">
      <polyline stroke="currentColor" points="2,7.1 6.5,11.1 11,7.1 "/>
      <line stroke="currentColor" x1="6.5" y1="1.2" x2="6.5" y2="10.3"/>
    </svg> <span class="button-text">提 交</span>
  </div>
  
  <div class="message loadingMessage">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 17">
      <circle class="loadingCircle" cx="2.2" cy="10" r="1.6"/>
      <circle class="loadingCircle" cx="9.5" cy="10" r="1.6"/>
      <circle class="loadingCircle" cx="16.8" cy="10" r="1.6"/>
    </svg>
  </div>
  
  <div class="message successMessage">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 11">
      <polyline stroke="currentColor" points="1.4,5.8 5.1,9.5 11.6,2.1 "/>
    </svg> <span class="button-text">成 功</span>
  </div>
</button>
  <canvas id="canvas"></canvas>
</body>
<script>
  const confettiCount = 20
const sequinCount = 10

const gravityConfetti = 0.3
const gravitySequins = 0.55
const dragConfetti = 0.075
const dragSequins = 0.02
const terminalVelocity = 3

const button = document.getElementById('button')
var disabled = false
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
let cx = ctx.canvas.width / 2
let cy = ctx.canvas.height / 2

let confetti = []
let sequins = []

const colors = [
  { front : '#7b5cff', back: '#6245e0' }, // Purple
  { front : '#b3c7ff', back: '#8fa5e5' }, // Light Blue
  { front : '#5c86ff', back: '#345dd1' }  // Darker Blue
]

randomRange = (min, max) => Math.random() * (max - min) + min

initConfettoVelocity = (xRange, yRange) => {
  const x = randomRange(xRange[0], xRange[1])
  const range = yRange[1] - yRange[0] + 1
  let y = yRange[1] - Math.abs(randomRange(0, range) + randomRange(0, range) - range)
  if (y >= yRange[1] - 1) {
    y += (Math.random() < .25) ? randomRange(1, 3) : 0
  }
  return {x: x, y: -y}
}

function Confetto() {
  this.randomModifier = randomRange(0, 99)
  this.color = colors[Math.floor(randomRange(0, colors.length))]
  this.dimensions = {
    x: randomRange(5, 9),
    y: randomRange(8, 15),
  }
  this.position = {
    x: randomRange(canvas.width/2 - button.offsetWidth/4, canvas.width/2 + button.offsetWidth/4),
    y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
  }
  this.rotation = randomRange(0, 2 * Math.PI)
  this.scale = {
    x: 1,
    y: 1,
  }
  this.velocity = initConfettoVelocity([-9, 9], [6, 11])
}
Confetto.prototype.update = function() {
  this.velocity.x -= this.velocity.x * dragConfetti
  this.velocity.y = Math.min(this.velocity.y + gravityConfetti, terminalVelocity)
  this.velocity.x += Math.random() > 0.5 ? Math.random() : -Math.random()
  
  this.position.x += this.velocity.x
  this.position.y += this.velocity.y

  this.scale.y = Math.cos((this.position.y + this.randomModifier) * 0.09)    
}

function Sequin() {
  this.color = colors[Math.floor(randomRange(0, colors.length))].back,
  this.radius = randomRange(1, 2),
  this.position = {
    x: randomRange(canvas.width/2 - button.offsetWidth/3, canvas.width/2 + button.offsetWidth/3),
    y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
  },
  this.velocity = {
    x: randomRange(-6, 6),
    y: randomRange(-8, -12)
  }
}
Sequin.prototype.update = function() {
  this.velocity.x -= this.velocity.x * dragSequins
  this.velocity.y = this.velocity.y + gravitySequins
  
  this.position.x += this.velocity.x
  this.position.y += this.velocity.y   
}

initBurst = () => {
  for (let i = 0; i < confettiCount; i++) {
    confetti.push(new Confetto())
  }
  for (let i = 0; i < sequinCount; i++) {
    sequins.push(new Sequin())
  }
}

render = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  
  confetti.forEach((confetto, index) => {
    let width = (confetto.dimensions.x * confetto.scale.x)
    let height = (confetto.dimensions.y * confetto.scale.y)
    
    ctx.translate(confetto.position.x, confetto.position.y)
    ctx.rotate(confetto.rotation)

    confetto.update()
    
    ctx.fillStyle = confetto.scale.y > 0 ? confetto.color.front : confetto.color.back
    
    ctx.fillRect(-width / 2, -height / 2, width, height)
    
    ctx.setTransform(1, 0, 0, 1, 0, 0)

    if (confetto.velocity.y < 0) {
      ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
    }
  })

  sequins.forEach((sequin, index) => {  
    ctx.translate(sequin.position.x, sequin.position.y)
    
    sequin.update()
    
    ctx.fillStyle = sequin.color
    
    ctx.beginPath()
    ctx.arc(0, 0, sequin.radius, 0, 2 * Math.PI)
    ctx.fill()

    ctx.setTransform(1, 0, 0, 1, 0, 0)

    if (sequin.velocity.y < 0) {
      ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
    }
  })

  confetti.forEach((confetto, index) => {
    if (confetto.position.y >= canvas.height) confetti.splice(index, 1)
  })
  sequins.forEach((sequin, index) => {
    if (sequin.position.y >= canvas.height) sequins.splice(index, 1)
  })

  window.requestAnimationFrame(render)
}

clickButton = () => {
  if (!disabled) {
    disabled = true
    button.classList.add('loading')
    button.classList.remove('ready')
    setTimeout(() => {
      button.classList.add('complete')
      button.classList.remove('loading')
      setTimeout(() => {
        window.initBurst()
        setTimeout(() => {
          disabled = false
          button.classList.add('ready')
          button.classList.remove('complete')
        }, 4000)
      }, 320)
    }, 1800)
  }
}

resizeCanvas = () => {
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight
  cx = ctx.canvas.width / 2
  cy = ctx.canvas.height / 2
}

window.addEventListener('resize', () => {
  resizeCanvas()
})

document.body.onkeyup = (e) => {
  if (e.keyCode == 13 || e.keyCode == 32) {
    clickButton()
  }
}

textElements = button.querySelectorAll('.button-text')
textElements.forEach((element) => {
  characters = element.innerText.split('')
  let characterHTML = ''
  characters.forEach((letter, index) => {
    characterHTML += `<span class="char${index}" style="--d:${index * 30}ms; --dr:${(characters.length - index - 1) * 30}ms;">${letter}</span>`
  })
  element.innerHTML = characterHTML
})

window.initBurst()
render()
</script>

</html>
小时候,看腻了农村的牛和马,长大后,来到了城里,才知道原来到处都是牛马!
全部回复0 显示全部楼层
暂无回复,精彩从你开始!

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于楼主

管理员
  • 主题

    710
  • 回答

    248
  • 积分

    1903
虚位以待,此位置招租

商务推广

    网盘拉新-短剧推广 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租