【Vue粒子动效】vue-particles实现一个科技粒子动画登录页,Picker It~

963 阅读1分钟

安装 vue-particles

npm install vue-particles --save-dev

引入

main.ts

import Vue from 'vue'
import VueParticles from 'vue-particles'
Vue.use(VueParticles)

属性

别人总结的,直接拿来了,方便查看

  • particlesNumber: Number类型。默认80。粒子数量。
  • shapeType: String类型。默认'circle'。可用的粒子外观类型有:"circle","edge","triangle", "polygon","star"。
  • particleSize: Number类型。默认80。单个粒子大小。
  • linesColor: String类型。默认'#dedede'。线条颜色。
  • linesWidth: Number类型。默认1。线条宽度。
  • lineLinked: 布尔类型。默认true。连接线是否可用。
  • lineOpacity: Number类型。默认0.4。线条透明度。
  • linesDistance: Number类型。默认150。线条距离。
  • moveSpeed: Number类型。默认3。粒子运动速度。
  • hoverEffect: 布尔类型。默认true。是否有hover特效。
  • hoverMode: String类型。默认true。可用的hover模式有: "grab", "repulse", "bubble"。
  • clickEffect: 布尔类型。默认true。是否有click特效。
  • clickMode: String类型。默认true。可用的click模式有: "push", "remove", "repulse", "bubble"。

重要在实践

image.png

BackgroundParticles.vue:

子组件

<template>
  <vue-particles
    id="bg-particles"
    :clickEffect="true"
    :options="particlesOpts"
  />
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { ParticlesComponent as VueParticles } from 'particles.vue3'

export default defineComponent({
  name: 'BackgroundParticles',
  components: {
    VueParticles,
  },
  setup() {
    const particlesOpts = {
      particles: {
        number: {
          value: 50,
          density: {
            enable: true,
            valueArea: 400,
          },
        },
        color: {
          value: '#3CB2BE',
        },
        shape: {
          type: 'circle',
          stroke: {
            width: 0,
            color: '#000000',
          },
          polygon: {
            nbSides: 5,
          },
        },
        opacity: {
          value: 1,
          random: false,
          anim: {
            enable: false,
            speed: 1,
            opacityMin: 0.1,
            sync: false,
          },
        },
        size: {
          value: 5,
          random: true,
          anim: {
            enable: false,
            speed: 40,
            sizeMin: 0.1,
            sync: false,
          },
        },
        lineLinked: {
          enable: true,
          distance: 160,
          color: '#3CB2BE',
          opacity: 0.5,
          width: 1,
        },
        move: {
          enable: true,
          speed: 1,
          direction: 'none',
          random: true,
          straight: false,
          outMode: 'out',
          bounce: false,
          attract: {
            enable: false,
            rotateX: 600,
            rotateY: 1200,
          },
        },
      },
      interactivity: {
        detectsOn: 'canvas',
        events: {
          onHover: {
            enable: true,
            mode: 'bubble',
          },
          onClick: {
            enable: false,
            mode: 'push',
          },
          resize: true,
        },
        modes: {
          grab: {
            distance: 400,
            lineLinked: {
              opacity: 1,
            },
          },
          bubble: {
            distance: 400,
            size: 8,
            duration: 10,
            opacity: 0.248,
            speed: 3,
          },
          repulse: {
            distance: 200,
            duration: 0.4,
          },
          push: {
            particlesNb: 4,
          },
          remove: {
            particlesNb: 2,
          },
        },
      },
      detectRetina: true
    }

    return {
      particlesOpts,
    }
  },
})
</script>

<style scoped>
#bg-particles {
  position: absolute;
  width: 100%;
  height: 100%;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  background-size: cover;
}
</style>
Login.vue:

父组件

<template>
  <div class="login-container">
    <background-particles/>
    <n-form
        ref="loginFormRef"
        :model="loginForm"
        :rules="loginRules"
        label-placement="left"
        size="small"
        class="login-form"
    >
      <h1 class="form-title">智慧城市3D可视化平台</h1>
      <n-form-item path="username">
        <n-input
            v-model:value="loginForm.username"
            placeholder="用户名"
            type="text"
            size="large"
            :style="inputStyles"
        >
          <template #prefix>
            <n-icon>
              <IconUser/>
            </n-icon>
          </template>
        </n-input>
      </n-form-item>
      <n-form-item path="password">
        <n-tooltip :show="capsTooltip" placement="top-start">
          <template #trigger>
            <n-input
                v-model:value="loginForm.password"
                placeholder="请输入密码"
                type="password"
                size="large"
                show-password-on="click"
                :style="inputStyles"
                @keydown="checkCapslock"
                @blur="capsTooltip = false"
                @keyup.enter="handleLogin"
            >
              <template #prefix>
                <n-icon>
                  <IconKey/>
                </n-icon>
              </template>
            </n-input>
          </template>
          <span> 大写锁定已打开 </span>
        </n-tooltip>
      </n-form-item>
      <n-button
          :loading="loading"
          type="primary"
          size="large"
          style="width: 100%; margin-bottom: 20px;"
          @click="handleLogin"
      >
        登录
      </n-button>
      <div style="position: relative;">
        <div class="tips">
          <span class="tips-text">忘记密码?</span>
        </div>
        <div class="lang-select">
          <span class="lang-chang">中英切换</span>
          <g-lang-select/>
        </div>
      </div>
    </n-form>
  </div>
</template>

<script lang='ts'>
  import {defineComponent, ref, watch, defineAsyncComponent} from 'vue'
  import {useRouter, useRoute} from 'vue-router'
  import {UserStore} from '@/domains/user'
  import {IconUser, IconKey} from '@/icons'

  const validateUsername = (rule: any, value: string, callback: Function) => {
    if (!['admin', 'editor'].includes(value)) {
      callback(new Error('请输入正确的用户名'))
    } else {
      callback()
    }
  }

  const validatePassword = (rule: any, value: string, callback: Function) => {
    if (value.length < 6) {
      callback(new Error('密码不能少于6位'))
    } else {
      callback()
    }
  }

  const getOtherQuery = (query: any) => {
    return Object.keys(query).reduce((acc: any, cur) => {
      if (cur !== 'redirect') {
        acc[cur] = query[cur]
      }
      return acc
    }, {})
  }

  export default defineComponent({
    name: 'Login',
    components: {
      BackgroundParticles: defineAsyncComponent(() => import('./background-particles.vue')),
      IconUser,
      IconKey,
    },
    setup() {
      const loginForm = ref({
        username: 'admin',
        password: '123456',
      })

      const loginRules = ref({
        username: [{required: true, trigger: 'blur', validator: validateUsername}],
        password: [{required: true, trigger: 'blur', validator: validatePassword}],
      })

      const loginFormRef = ref(null)
      const capsTooltip = ref(false)
      const loading = ref(false)
      const redirect = ref('')
      const otherQuery = ref({})

      const inputStyles = {
        '--caret-color': '#fff',
        '--text-color': '#fff',
        '--border': '1px solid rgba(76, 206, 240, 0.5)',
        '--color': 'transparent',
        '--color-focus': 'transparent',
        '--color-focus-error': 'transparent',
        '--color-focus-warning': 'transparent',
      }

      const route = useRoute()
      const router = useRouter()

      const checkCapslock = ({shiftKey, key}: any) => {
        if (key && key.length === 1) {
          if (shiftKey && (key >= 'a' && key <= 'z') || !shiftKey && (key >= 'A' && key <= 'Z')) {
            capsTooltip.value = true
          } else {
            capsTooltip.value = false
          }
        }

        if (key === 'CapsLock' && capsTooltip.value === true) {
          capsTooltip.value = false
        }
      }

      const handleLogin = () => {
        (loginFormRef.value as any).validate((errors: any) => {
          if (!errors) {
           // ....
        })
      }

      return {
        loginForm,
        loginRules,
        loginFormRef,
        capsTooltip,
        inputStyles,
      }
    },
  })
</script>

<style lang="scss">
  .login-container {
    width: 100%;
    min-height: 100%;
    height: 100vh;
    overflow: hidden;
    background-color: #0F2133;
    position: relative;
    color: #ffffff;
    
    .login-form {
      width: 500px;
      max-width: 100%;
      padding: 25px 40px;
      height: 360px;
      margin: 10% auto;
      overflow: hidden;
      background-color: rgba(0, 0, 0, 0.22);
      border: 2px solid;
      -moz-border-image:url("src/assets/login/form-bg-border.png") stretch; /* Old Firefox */
      -webkit-border-image:url("src/assets/login/form-bg-border.png") stretch;  /* Safari and Chrome */
      -o-border-image:url("src/assets/login/form-bg-border.png") stretch;   /* Opera */
      border-image:url("src/assets/login/form-bg-border.png")  stretch;
      -moz-box-shadow: 1px 1px 12px rgba(76, 206, 240, 0.5);
      -webkit-box-shadow: 1px 1px 12px rgba(76, 206, 240, 0.5);
      box-shadow: 1px 1px 12px rgba(76, 206, 240, 0.5);
      border: 1px solid rgba(76, 206, 240, 0.5);

      .form-title {
        font-size: 36px;
        color: #ffffff;
        font-family: 'YouSheBiaoTiHei' !important;
        text-align: left;
        padding-bottom: 10px;
        font-weight: normal;
      }
    }

    .tips {
      margin-bottom: 10px;
      font-size: 14px;
      color: #fff;

      span {
        margin-right: 16px;
      }

      .tips-text {
        color: #ffffff;
      }
    }

    .lang-select {
      float: right;
      margin-top: -24px;
      cursor: pointer;
      color: #ffffff;
      font-size: 14px;

      .lang-chang {
        padding-right: 10px;
      }
    }
  }
</style>