SOURCE

console 命令行工具 X clear

                    
>
console
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>p5js-ball-demo</title>
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport">
    <meta name="renderer" content="webkit">
    <style>
      * {
        margin: 0;
        padding: 0;
        list-style: none;
      }
      body {
        font-size: 14px;
        font-family: sans-serif,"HelveticaNeue",Helvetica,"PingFangSC","MicrosoftYaHei","HiraginoSansGB",Arial;
        color: #27282b;
        background: #34363f;
      }
      a {
        text-decoration: none;
        color: #27282b;
      }
      *,
      *::before,
      *::after {
        outline: none;
        box-sizing: border-box;
      }
      html,
      body {
        height: 100vh;
        overflow: hidden;
      }
      .setting-panel {
        width: 200px;
        height: 270px;
        overflow-y: auto;
        padding: 20px 20px;
        background: #23252af7;
        user-select: none;
        border-radius: 3px;
        position: fixed;
        top: -1000px;
        left: -1000px;
      }
      .setting-panel::-webkit-scrollbar {
        width: 3px;
      }
      .setting-panel:hover::-webkit-scrollbar-thumb {
        background-color: #191a1c;
      }
      .d-item:not(:last-child) {
        margin-bottom: 12px;
      }
      .d-item .s-tit {
        display: inline-block;
        font-size: 12px;
        color: #d5d5d5;
        margin-bottom: 8px;
      }
      .d-item-color {
        margin-bottom: 0;
      }
      input[type="number"]::-webkit-outer-spin-button,
      input[type="number"]::-webkit-inner-spin-button {
        -webkit-appearance: none !important;
        margin: 0;
      }
      .inp-range {
        display: block;
        width: 100%;
        height: 2px;
        border-radius: 50px;
        background: #7774;
        -webkit-appearance: none;
      }
      .inp-range::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 10px;
        height: 10px;
        border-radius: 55%;
        background: #b5b6b7;
      }
      .inp-range:hover::-webkit-slider-thumb {
        background: #e0e0e0;
      }
      .inp-color {
        border: 0;
        width: 18px;
        height: 18px;
        border-radius: 55%;
        margin-left: 5px;
        background: #e7e7e7;
      }
      .inp-text {
        width: 50px;
        height: 15px;
        padding: 0 3px;
        color: #d5d5d5;
        background: transparent;
        border-radius: 2px;
        border: 0;
      }
      .lb-ckb {
        width: 100%;
      }
      .inp-checkbox {
        width: 12px;
        height: 12px;
        margin-right: 2px;
        vertical-align: -1px;
      }
    </style>
  </head>

  <body>
    <div class="wrapper" id="app">
      <div class="setting-panel" :style="stStyle" v-show="showSettingPanel">
        <div class="d-item">
          <p class="s-tit">小球个数: <input class="inp-text" type="number" v-model="dp.pCount"></p>
          <input class="inp-range" type="range" min="0" max="200" step="5" v-model.number="dp.pCount">
        </div>
        <div class="d-item">
          <p class="s-tit">小球大小: <input class="inp-text" type="number" v-model="dp.pSize"></p>
          <input class="inp-range" type="range" min="0" max="50" step="2" v-model.number="dp.pSize">
        </div>
        <div class="d-item d-item-color">
          <p class="s-tit">小球颜色: </p>
          <input class="inp-color" type="color" v-model="dp.pColor">
        </div>
        <div class="d-item d-item-color">
          <label class="s-tit lb-ckb" for="inprndxs">
            <input class="inp-text inp-checkbox" id="inprndxs" type="checkbox" v-model="dp.randColor"> 
            随机颜色
          </label>
        </div>
        <div class="d-item">
          <p class="s-tit">小球初始速度: <input class="inp-text" type="text" v-model.number="dp.pSpeed"></p>
          <input class="inp-range" type="range" min="0" max="30" step="1" v-model.number="dp.pSpeed">
        </div>
        <div class="d-item">
          <p class="s-tit">小球初始位置: {{ getPosText }}</p>
          <input class="inp-range" type="range" min="0" max="4" step="1" v-model.number="dp.pPos">
        </div>
        <div class="d-item">
          <p class="s-tit">摩擦系数: <input class="inp-text" type="number" step="0.05" v-model="dp.friction"></p>
          <input class="inp-range" type="range" min="0" max="1" step="0.05" v-model.number="dp.friction">
        </div>
        <div class="d-item">
          <p class="s-tit">重力加速度: <input class="inp-text" type="number" v-model="dp.gravity"></p>
          <input class="inp-range" type="range" min="0" max="20" step="1" v-model.number="dp.gravity">
        </div>
        <div class="d-item">
          <label class="s-tit lb-ckb" for="inpau2s">
            <input class="inp-text inp-checkbox" id="inpau2s" type="checkbox" v-model="dp.isPause"> 
            暂停
          </label>
        </div>
      </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.global.prod.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/p5@1.2.0/lib/p5.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vio-utils/utils.min.js"></script>
    <script>
      const particleArr = []
      //小球之间是否碰撞检测, 
      
      const vm = Vue.createApp({
        setup(props, ctx) {
          const { ref, reactive, onMounted, watch, computed } = Vue
          const dp = reactive({
            // 小球个数
            pCount: 50,
            // 小球大小
            pSize: 12,
            // 小球颜色
            pColor: '#dddddd',
            // 是否随机颜色
            randColor: true,
            // 小球初始速度
            pSpeed: 5,
            // 小球初始位置 上右下左中: 1,2,3,4,0
            pPos: 1,
            // 摩擦系数
            friction: 0,
            // 重力加速度
            gravity: 4,
            // 是否暂停
            isPause: false,
          })
          const clientX = ref(0)
          const clientY = ref(0)
          const showSettingPanel = ref(false)
          onMounted(() => {
            document.addEventListener('contextmenu', (e) => {
              showSettingPanel.value = true
              clientX.value = e.clientX
              clientY.value = e.clientY
              e.preventDefault()
            })
            document.addEventListener('click', (e) => {
              if (!document.querySelector('.setting-panel').contains(e.target)) {
                showSettingPanel.value = false
              }
            })
          })
          
          watch(() => dp.pCount, (nVal, oVal) => {
            dp.pCount = max(0, min(1000, nVal))
          })
          watch(() => dp.pSize, (nVal, oVal) => {
            dp.pSize = max(0, min(50, nVal))
          })
          watch(() => dp.friction, (nVal, oVal) => {
            dp.friction = max(0, min(1, nVal))
          })
          watch(() => dp.gravity, (nVal, oVal) => {
            dp.gravity = max(0, min(20, nVal))
          })
          watch(() => dp.pSpeed, (nVal, oVal) => {
            initPArr()
          })
          watch(() => dp.pPos, (nVal, oVal) => {
            initPArr()
          })
          watch(() => dp.isPause, (nVal, oVal) => {
            nVal ? noLoop() : loop()
          })
          
          const calcGravity = computed(() => {
            return dp.gravity / 5
          })
          const stStyle = computed(() => {
            return {
              left: clientX.value + 'px',
              top: clientY.value + 'px',
            }
          })
          const getPosText = computed(() => {
            const ob = {
              0: '中',
              1: '上',
              2: '右',
              3: '下',
              4: '左',
            }
            return ob[dp.pPos]
          })
          
          return {
            dp,
            calcGravity,
            stStyle,
            getPosText,
            showSettingPanel,
          }
        },
      }).mount('#app')
      
      function setup() {
        createCanvas(window.innerWidth, window.innerHeight)
        initPArr()
      }
      
      function draw() {
        background('#34363f')
        updatePArr()
        particleArr.forEach((v, i) => {
          v.draw(i)
          v.update()
        })
      }
      
      function windowResized() {
        resizeCanvas(windowWidth, windowHeight)
      }
      
      function initPArr() {
        particleArr.length = 0
        for (let i = 0; i < vm.dp.pCount; i++) {
          particleArr.push(new Particle())
        }
      }
      function updatePArr() {
        let diff = vm.dp.pCount - particleArr.length
        if (diff > 0) {
          for (let i = 0; i < diff; i++) {
            particleArr.push(new Particle())
          }
        } else {
          particleArr.length = vm.dp.pCount
        }
      }
      
      class Particle {
        constructor() {
          const r = randomGaussian
          const obj = {
            0: [r(width / 2), r(height / 2)],
            1: [r(width / 2), r(30)],
            2: [r(width - 30), r(height / 4)],
            3: [r(width / 2), r(height - 30)],
            4: [r(30), r(height / 4)],
          }
          let x = obj[vm.dp.pPos][0]
          let y = obj[vm.dp.pPos][1]
          let speed = vm.dp.pSpeed
          this.isFreeze = false
          this.speed = createVector(random(-speed, speed), random(-speed, speed))
          this.pos = createVector(x, y)
          this.size = vm.dp.pSize
          this.color = vio.randColor()
        }
        // 绘制
        draw(i) {
          noStroke()
          const x = max(this.size / 2, min(width - this.size / 2, this.pos.x))
          const y = max(this.size / 2, min(height - this.size / 2, this.pos.y))
          const pColor = vm.dp.randColor ? this.color : vm.dp.pColor
          circle(x, y, this.size)
          fill(pColor)
        }
        // 更新
        update() {
          this.size = vm.dp.pSize
          this.edgesDetection()
          this.accSpeed()
          if (this.isFreeze) {
            this.speed.x = 0
            this.speed.y = 0
            return
          }
          this.pos.add(this.speed)
        }
        // 边缘检测
        edgesDetection() {
          let fc = vm.dp.friction
          let isBottom = this.pos.y >= height - this.size / 2
          if (this.pos.x <= this.size / 2 || this.pos.x >= width - this.size / 2) {
            // 当摩擦系数为1时停止运动
            if (fc === 1 && vm.dp.gravity === 0) {
              this.isFreeze = true
              return
            }
            this.speed.x *= -(1 - fc / 2)
            this.speed.y *= 1 - fc / 50
          }
          if (this.pos.y <= this.size / 2 || isBottom) {
            if (fc === 1 && isBottom) {
              this.isFreeze = true
              return
            }
            if (this.speed.y > 0.1 || !vm.calcGravity) {
              this.speed.x *= 1 - fc / 50
              this.speed.y *= -(1 - fc / 2)
            }
          }
        }
        // 重力加速度
        accSpeed() {
          this.speed.y += vm.calcGravity
        }
      }
      
    </script>
  </body>
</html>