下拉刷新
OneFile
源码
looptap消磨时间的小游戏,把球停在有颜色区域
作者 vasanth·主语言 HTML·有依赖·6.5k 次查看
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <meta name="author" content="Vasanth.V" /> <meta name="theme-color" content="#FFFFFF" /> <link rel="apple-touch-icon" sizes="180x180" href="https://img.hellogithub.com/favicon/apple-touch-icon.png"> <link rel="android-chrome" sizes="192x192" href="https://img.hellogithub.com/favicon/android-chrome-192x192.png"> <link rel="android-chrome" sizes="512x512" href="https://img.hellogithub.com/favicon/android-chrome-512x512.png"> <link rel="icon" type="image/png" sizes="32x32" href="https://img.hellogithub.com/favicon/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="https://img.hellogithub.com/favicon/favicon-16x16.png"> <link rel="icon" href="https://img.hellogithub.com/favicon/favicon.ico"> <title>Looptap - 消磨时间的小游戏</title> <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script> <style> html, body { overscroll-behavior: none; } body { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif; margin: 0; line-height: 1.5; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; background: #fbf9f6; } a { color: inherit; } #canvas { display: none; height: 100vh; width: 100%; max-width: 640px; overflow: hidden; box-sizing: border-box; margin: auto; flex-direction: column; justify-content: center; } #canvas svg { width: 100%; } #about { text-align: center; font-size: 1.2rem; line-height: 1.75; padding: 0rem 2rem 1rem; color: #2c3d51; } #about a { font-weight: 500; font-style: italic; } #ball { transform: translateZ(0); animation: rot 2000ms infinite linear; animation-play-state: paused; } #ball.started { animation-play-state: running; } #play { cursor: pointer; } #finalscore, #best { font-size: 70%; fill: #34495e; } #best { font-style: italic; font-size: 50%; } #tip { font-weight: 300; font-size: 18%; font-style: italic; padding: 0.5rem 2rem 0rem; } #shareBtn { background: #1d9bf0; color: #fff; width: 150px; text-align: center; text-decoration: none; border-radius: 2rem; padding: 0.3rem 1.5rem; margin: 1rem auto 0px; font-size: 1.25rem; } #shareBtn.hide { visibility: hidden; } @keyframes rot { from { transform: rotate(0deg) translate(40%) rotate(0deg); } to { transform: rotate(360deg) translate(40%) rotate(-360deg); } } </style> </head> <body> <section id="canvas" v-bind:style="'display:flex;'"> <svg id="looptap" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <rect id="bg" x="0" cy="0" height="100" width="100" fill="none" /> <text x="50" y="50" dominant-baseline="middle" text-anchor="middle" class="score" id="score" v-if="state === 'started'" v-html="score"></text> <text x="50" y="32" text-anchor="middle" class="score" id="finalscore" v-if="state === 'stopped'" v-html="score"></text> <text x="50" y="70" text-anchor="middle" class="score" id="best" v-if="state === 'stopped'" v-html="'Best: '+best"></text> <g id="tip" v-if="state === 'init'"> <text x="50" y="68" text-anchor="middle" class="tip"> 点击 ▶️ 开始 / 点击任何地方停止 </text> <text x="50" y="74" text-anchor="middle" class="tip"> 让球停在有颜色的区域就行! </text> </g> <path id="arc" fill="none" v-bind:stroke="colors[Math.floor(score / 10)] || colors[Math.floor((score - 270) / 10)] || '#bdc3c7'" stroke-width="10" stroke-linejoin="round" stroke-linecap="round" v-bind:d="arcDValue" /> <circle id="ball" cx="50" cy="50" r="4" fill="#2C3D51" v-bind:class="state" v-bind:style="'animation-duration: '+(2000 - taps * 40) + 'ms'" /> <polygon id="play" points="45,45 55,50 45,55" fill="#2C3D51" stroke="#2C3D51" stroke-width="5" stroke-linejoin="round" stroke-linecap="round" v-if="state !== 'started'" v-on:click="startPlay" /> </svg> <a id="shareBtn" v-bind:href="'https://service.weibo.com/share/share.php?url=https%3A%2F%2Fhellogithub.com%2Fonfile%2Fcode%2Fcc759276aefe4bad87ac259940042581&title=%E6%88%91%E7%9A%84%E5%88%86%E6%95%B0%EF%BC%9A'+score+'%EF%BC%8CLooptap%20-%20%E6%B6%88%E7%A3%A8%E6%97%B6%E9%97%B4%E7%9A%84%E5%B0%8F%E6%B8%B8%E6%88%8F%E3%80%82'" v-if="['stopped', 'started'].includes(state)" target="_blank" v-bind:class="state === 'started' ? 'hide' : ''"> 分享你的分数 </a> <div id="about" v-if="state === 'init'"> 源码: <a href="https://github.com/vasanthv/looptap" target="_blank">Vasanth.V</a><br /> 灵感来自 iOS 游戏 Coogyloop </div> </section> </body> <script> const loopTapApp = new Vue({ el: "#canvas", data: { arc: [180, 270], taps: 0, score: 0, best: window.localStorage.best || 0, state: "init", prevTapTime: 0, colors: [ '#F26D22', '#F1B21D', '#ECE82F', '#AED136', '#7EC242', '#63BC46', '#65BD5D', '#64C29A', '#61C8D2', '#2DAAE1', '#456EB5', '#4451A3', '#6151A2' ] }, computed: { arcDValue: function () { return this.describeArc(50, 50, 40, this.arc[0], this.arc[1]); }, }, methods: { polarToCartesian: function (centerX, centerY, radius, angleInDegrees) { const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0; return { x: centerX + radius * Math.cos(angleInRadians), y: centerY + radius * Math.sin(angleInRadians), }; }, describeArc: function (x, y, radius, startAngle, endAngle) { const start = this.polarToCartesian(x, y, radius, endAngle); const end = this.polarToCartesian(x, y, radius, startAngle); const arcFlag = endAngle - startAngle <= 180 ? "0" : "1"; const d = ["M", start.x, start.y, "A", radius, radius, 0, arcFlag, 0, end.x, end.y].join(" "); return d; }, getAngle: function (cx, cy, ex, ey) { const dy = ey - cy; const dx = ex - cx; let theta = Math.atan2(dx, -dy); theta *= 180 / Math.PI; theta = theta < 0 ? theta + 360 : theta; return theta; }, getBallAngle: function () { const bg = document.getElementById("bg").getBoundingClientRect(); const bgCenter = { x: bg.left + bg.width / 2, y: bg.top + bg.height / 2 }; const ball = document.getElementById("ball").getBoundingClientRect(); const ballCenter = { x: ball.left + ball.width / 2, y: ball.top + ball.height / 2 }; return this.getAngle(bgCenter.x, bgCenter.y, ballCenter.x, ballCenter.y); }, setArc: function () { const random = (i, j) => Math.floor(Math.random() * (j - i)) + i; arc = []; arc.push(random(0, 300)); arc.push(random(arc[0] + 10, arc[0] + 110)); arc[1] = arc[1] > 360 ? 360 : arc[1]; this.arc = arc; }, startPlay: function () { this.state = "started"; this.taps = 0; this.score = 0; this.prevTapTime = Date.now(); }, stopPlay: function () { if (this.state === "started") { this.state = "stopped"; if (this.score > this.best) window.localStorage.best = this.best = this.score; } }, tap: function (e) { e.preventDefault(); e.stopPropagation(); if (this.state === "started") { const ballAngle = this.getBallAngle(); // adding a 6 for better accuracy as the arc stroke extends beyond the angle. if (ballAngle + 6 > this.arc[0] && ballAngle - 6 < this.arc[1]) { const currentTapTime = Date.now(); const tapInterval = currentTapTime - this.prevTapTime; this.taps++; this.score = this.score + (tapInterval < 500 ? 5 : tapInterval < 1000 ? 2 : 1); this.prevTapTime = currentTapTime; this.setArc(); } else this.stopPlay(); } }, }, }); if ("ontouchstart" in window) { window.addEventListener("touchstart", loopTapApp.tap); } else { window.addEventListener("mousedown", loopTapApp.tap); window.onkeypress = (e) => { if (e.keyCode == 32) { if (loopTapApp.state === "stopped") { loopTapApp.startPlay(); } else { loopTapApp.tap(e); } } }; } </script> </html>