Toggle navigation
在线编辑器
在线代码
文本比较
jQuery下载
前端库
在线手册
登录/注册
下载代码
html
css
js
分享到微信朋友圈
X
html
tap to start
css
* { box-sizing: border-box; } body { background: #222; margin: 0; width: 100vw; height: 100vh; overflow: hidden; } nav { background: #000; position: absolute; width: 64px; bottom: 0; top: 0; } nav svg { width: 64px; height: 64px; } main svg { width: 100%; height: 100%; } main { position: absolute; left: 64px; top: 0; cursor: crosshair; bottom: 0; right: 0; } nav ul { z-index: 2; margin: 0; padding: 0; width: 100%; height: 100%; display: flex; flex-wrap: wrap; align-items: center; align-content: center; justify-content: center; list-style: none; } nav ul li { display: block; } nav a.selected > svg, nav a.selected > svg { background: #333; } svg.move { cursor: move; } #bounds { stroke: mediumseagreen; stroke-width: 1px } #cursor { stroke: white; stroke-width: 1px } #grid { stroke: #333; stroke-width: 1px; fill: none; } .hidden { display:none; } @media only screen and (orientation: landscape) { nav { height: 64px; width: 100vw; bottom: auto; } main { left: 0; top: 64px; right: 0; bottom: 0; } } .overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: none; background: rgba(0,0,0,0.5); } .overlay__tap2start { height: 100vh; font-family: sans-serif; color: #fff; display: flex; justify-content: center; align-items: center; font-size: 10vw; }
JavaScript
const isDebug = /debug/.test(window.location.href) const w3 = "http://www.w3.org/" const svgNS = w3 + "2000/svg" const xlinkNS = w3 + "1999/xlink" const notes = "C C# D D# E F F# G G# A A# B".split(" ") let w, h let scrollX = 0, scrollY = 0, cursorX = 0 const borders = { l: 0, r: 250 } const borderExtend = 250 let moveMode = null let playing = false const d = document const $ = document.querySelector.bind(d) const $$ = (sel, con) => Array.prototype.slice.call((con||d).querySelectorAll(sel)) const { sqrt, min, max } = Math const distance = (a, b) => sqrt((b[0]-a[0])**2 + (b[1]-a[1])**2) const freq = (y) => max(880 - y, 10) const freqToY = (f) => 880 - f const svg = $`#main` let AC, masterVolume const defaultVolume = 0.5 const initAudioContext = () => { AC = new AudioContext() if(!AC || AC.state === "suspended") { $`.overlay`.style.display='block' $`.overlay`.addEventListener('click', () => { $`.overlay`.style.display='none' initAudioContext() }) return } masterVolume = AC.createGain() masterVolume.gain.value = defaultVolume masterVolume.connect(AC.destination) } initAudioContext() const music = [] let mouseNoise = null let touchNoises = [] let midiNoises = {} let currentType = "sine" let clientRect = svg.getBoundingClientRect() Array.prototype.avg = function() { let r = 0, i = 0; for(i = 0; i < this.length; i++) r+=this[i] return r/this.length } const typeColors = { "sine": "#9944ff", "square": "#aad400", "sawtooth": "#c83737", "triangle": "#2a7fff", "noise": "#ffffff" } const noteToFreq = (note, octave) => { const n = (typeof note === "string") ? notes.indexOf(note.replace(/_/,'')) : n const f = (110*2**octave)*2**((n+3)/12) return f } const hexColor = (c) => { if (c.slice(0) === '#') return c if (c.slice(0,4) === 'rgb(') { return "#" + c.slice(4,-1).split(",").map(a => ((a|0)>>4).toString(16)+((a|0)%16).toString(16)).join("") } return c } const types = Object.keys(typeColors) const attribs = (el, attrs, x) => { for(x in attrs) if(attrs.hasOwnProperty(x) && attrs[x] !== undefined) el.setAttribute(x,attrs[x]) } const draw = (name,attrs) => { const el = document.createElementNS(svgNS, name) if(attrs) attribs(el,attrs) return el } const scrollToCursor = () => { const middleX = (clientRect.right - clientRect.left) / 2 if (cursorX > scrollX + middleX || scrollX > cursorX) { scrollX = cursorX - middleX } } const relPos = (x,y) => { // calculate from mouse/touch position // to svg coordinate const relativeX = scrollX + x - clientRect.left const relativeY = scrollY + y - clientRect.top return { relX: relativeX, relY: relativeY } } const userIsJamming = () => { return (!!mouseNoise) || touchNoises.length > 0 || Object.keys(midiNoises).length > 0 } const setCursor = (x) => { if (userIsJamming() && x < cursorX) { return } cursorX = x scrollToCursor() setViewBox() } const SoundMachine = { // Helper factory that creates and reuses // oscillators. Usage: // SoundMachine.noise(frequency, type) // returns a noise that starts playing // immediately. It does not create a // new oscillator each time. After a noise // is muted, it can be reused, avoiding // memory issues. noises: [], whiteNoiseBuffer: null, createWhiteNoise: () => { if (! SoundMachine.whiteNoiseBuffer) { const len = AC.sampleRate * 2 const buf = AC.createBuffer(1, AC.sampleRate, len) const data = buf.getChannelData(0) for (let i = 0; i < len; i++) { data[i] = Math.random() * 2 - 1 } SoundMachine.whiteNoiseBuffer = buf } const bufSrc = AC.createBufferSource() bufSrc.buffer = SoundMachine.whiteNoiseBuffer bufSrc.loop = true bufSrc.playbackRate.value = 1.0 return bufSrc }, makeSomeNoise: (freq, type) => { const noise = { osc: type === "noise" ? SoundMachine.createWhiteNoise() : AC.createOscillator(), env: AC.createGain() } const { osc, env } = noise if (type !== "noise") { osc.type = type osc.frequency.value = freq osc.detune.value = 0 } env.gain.value = 1.0 osc.start() osc.connect(env) env.connect(masterVolume) SoundMachine.noises.push(noise) return noise }, noise: (freq, type) => { // reuse a noise we muted before const noise = SoundMachine.noises.find(n => { return n.env.gain.value === 0.0 && n.osc.type === type }) if (! noise) { // create new noise return SoundMachine.makeSomeNoise(freq, type) } const { osc, env } = noise osc.frequency.setValueAtTime(freq, 0) // chromium issue 645776 osc.frequency.value = freq env.gain.value = 1.0 return noise } } class Noise { constructor(x,y,type) { this.element=draw("path",{ "d":"", "style": `stroke:${typeColors[type]};stroke-width:4;fill:none;` }) this.coords = [] this.type = type if (x) { if (typeof x === "object") { this.add(x) } else { if (isFinite(x) && y && isFinite(y)) this.add(x, y) } } svg.appendChild(this.element) this.render() } add(x, y, quiet) { this.lastX = x this.lastY = y const { relX, relY } = typeof x === "object" ? x : relPos(x, y) if (this.coords.length > 0) { const lastCoord = this.coords[this.coords.length - 1] const firstCoord = this.coords[0] if (distance([relX, relY], lastCoord) < 5) { return } if (firstCoord[0] < relX) { this.coords = this.coords.filter(c => c[0] < relX) } else { this.coords = this.coords.filter(c => c[0] > relX) } } this.coords.push([relX, relY]) if (relX > borders.r) borders.r+=borderExtend if (relX < borders.l) borders.l-=borderExtend this.render() if (quiet) { return } if (! this.noise) { this.noise = SoundMachine.noise(freq(relY), this.type) } if (this.type !== "noise") { this.noise.osc.frequency.value = freq(relY) } else { this.noise.osc.playbackRate.value = freq(relY) / 1e4 } } playAtX(x) { const { coords } = this const l = coords.length if (l < 2) { return } const isMuted = coords[0][0] > x || coords[l - 1][0] < x if (isMuted) { if (this.playerNoise) { this.playerNoise.env.gain.value=0.0 this.playerNoise = null } } else { const nearPoints = coords.filter((p) => Math.abs(p[0] - x) <= 5) const averageFreq = freq(nearPoints.map(p => p[1]).avg()) if(!isFinite(averageFreq)) { return } if (!this.playerNoise) { this.playerNoise = SoundMachine.noise(averageFreq, this.type) } else { if (this.type !== "noise") { this.playerNoise.osc.frequency.setValueAtTime(averageFreq, 0) // https://bugs.chromium.org/p/chromium/issues/detail?id=645776 this.playerNoise.osc.frequency.value = averageFreq } else { this.playerNoise.osc.playbackRate.value = averageFreq / 1e4 } } } } mute() { if (this.noise) { this.noise.env.gain.value=0.0 this.noise = null } if (this.playerNoise) { this.playerNoise.env.gain.value=0.0 this.playerNoise = null } } dispose() { this.mute() svg.removeChild(this.element) this.element = null } sortCoords() { this.coords.sort((a,b) => { if (a[0] > b[0]) return 1 if (a[0] < b[0]) return -1 if (a[0] == b[0]) return 0 }) this.render() } render() { const { coords } = this if (coords.length < 2) { return } this.element.setAttribute("d", "M"+coords[0].join(",")+ "L"+coords.slice(1).map(c=> c.join(",")).join("L")) } static fromPath(el) { const waveForms = Object.keys(typeColors).reduce((obj,key) => { obj[typeColors[key]] = key return obj },{}) const color = hexColor(el.style.stroke) const d = el.getAttribute("d") if (waveForms[color] && /M\d+\,\d+(L\d+\,\d+)+/.test(d)) { const n = new Noise(null, null, waveForms[color] || "sine") const coords = d.slice(1).split("L").map(p => p.split(',').map(x => x|0)) coords.forEach(c => n.add({relX: c[0], relY: c[1]}, null, true)) return n } } } const moveStart = (e) => { moveMode = { moving: true, x0: e.clientX, y0: e.clientY, scrollX0: scrollX, scrollY0: scrollY } } const moveDrag = (e) => { if (!moveMode.moving) { return } const { x0, y0, scrollX0, scrollY0 } = moveMode const dx = x0 - e.clientX const dy = y0 - e.clientY scrollX = scrollX0 + dx scrollY = scrollY0 + dy setViewBox() } const moveEnd = (e) => { moveMode = { moving: false } } const setMoveMode = (move, e) => { const btn = $('#moveBtn') if (move) { moveMode = e? { moving: true, x0: e.clientX, y0: e.clientY, scrollX0: scrollX, scrollY0: scrollY } : { moving: false } btn.classList.add("selected") svg.classList.add("move") } else { moveMode = null btn.classList.remove("selected") svg.classList.remove("move") } } $('#moveBtn').addEventListener("click", e => { setMoveMode(!!!moveMode) // "multiple exclamation marks", he went on, // shaking his head, "are a sure sign of // a diseased mind." // (Terry Pratchet in 'Eric') ? }) svg.addEventListener("mousedown", e => { if (touchNoises.length > 0) { // long tap on mobile // triggers contextmenu. // Additionally: mousedown // without a mouseup event. // this would result in a never-ending // beeeeeeeep on mobile return; } if (e.button === 1) { const { relX, relY } = relPos(e.clientX, e.clientY) setCursor(relX) e.preventDefault() return } if (!isDebug && e.button === 2) { setMoveMode(true, e) return } if (mouseNoise) { mouseNoise.dispose() mouseNoise = null } if (moveMode) { moveStart(e) return } mouseNoise = new Noise(e.clientX, e.clientY, currentType) }) svg.addEventListener("mousemove", e => { if (moveMode) { moveDrag(e) return } if (mouseNoise) { mouseNoise.add(e.clientX, e.clientY) } }) svg.addEventListener("mouseup", e => { if (moveMode) { moveEnd(e) if (e.button === 2) { setMoveMode(false) } return } if (mouseNoise) { mouseNoise.mute() mouseNoise.sortCoords() music.push(mouseNoise) mouseNoise = null } }) svg.addEventListener("touchstart", e => { if (moveMode) { setMoveMode(true, e.changedTouches[0]) return } Array.prototype.slice.call(e.changedTouches).map(t => { touchNoises.push({ id: t.identifier, noise: new Noise(t.clientX, t.clientY, currentType) }) }) }) const touch = (id) => touchNoises.find(el => el.id === id) svg.addEventListener("touchmove", e => { e.preventDefault() if (moveMode) { moveDrag(e.changedTouches[0]) return } Array.prototype.slice.call(e.changedTouches).map(t => { const touchObj = touch(t.identifier) if (touchObj) { touchObj.noise.add(t.clientX, t.clientY) } }) }) svg.addEventListener("touchend", e => { if (moveMode) { moveEnd(e) return } Array.prototype.slice.call(e.changedTouches).map((t, idx) => { const touchObj = touch(t.identifier) if (touchObj) { // touchObj.identifier = void 0 touchObj.noise.mute() touchObj.noise.sortCoords() music.push(touchObj.noise) touchObj.noise = null } }) touchNoises = touchNoises.filter(n => n.noise !== null) }) const setViewBox = () => { clientRect = svg.getBoundingClientRect() w = max(0, innerWidth - clientRect.left) h = max(0, innerHeight - clientRect.top) const bounds = $`#bounds` const x0 = scrollX - scrollX % (borderExtend/8) const y0 = scrollY - scrollY % (borderExtend/8) const nX = 2 + ((8*w / borderExtend)|0) const nY = 2 + ((8*h / borderExtend)|0) const gridLines = Array(nX).fill(0).map((e,i) => { return `M${x0+i*borderExtend/8},${scrollY}l0,${h}` }).join('') + Array(nY).fill(0).map((e,i) => { return `M${scrollX},${y0+i*borderExtend/8}l${w},0` }) bounds.setAttribute("d", `M${borders.l},${scrollY}l0,${h}` +`M${borders.r},${scrollY}l0,${h}`) const cursor = $`#cursor` cursor.setAttribute("d", `M${cursorX},${scrollY}l0,${h}`) svg.setAttribute("viewBox", [scrollX, scrollY, w, h]) const grid = $`#grid` grid.setAttribute("d", gridLines) } setViewBox() addEventListener("resize", setViewBox) addEventListener("contextmenu", e => { // prevent long-tap on touch screens // to trigger the context menu event. // Also prevent the context menu when // not in debug-mode because it is // used for moving the svg if (!isDebug || touchNoises.length > 0) { e.preventDefault() return false } }) const rewind = () => { scrollX = borders.l cursorX = borders.l setViewBox() } const play = () => { playing = true scrollX = 0 $('#playBtn').parentNode.classList.add('hidden') $('#pauseBtn').parentNode.classList.remove('hidden') } const pause = () => { playing = false music.forEach(beep => beep.mute()) $('#pauseBtn').parentNode.classList.add('hidden') $('#playBtn').parentNode.classList.remove('hidden') } $`#rewBtn`.addEventListener("click", rewind) $`#playBtn`.addEventListener("click", play) $`#pauseBtn`.addEventListener("click", pause) const waveBtns = $$('a[role=type]') waveBtns.map(btn => btn.addEventListener("click", (e) => { waveBtns.map(btn => btn.parentNode.classList.add("hidden")) let a = e.target while (a.nodeName.toLowerCase() !== 'a') { a = a.parentNode } const idx = waveBtns.indexOf(a) const nextA = waveBtns[(idx+1) % waveBtns.length] currentType = nextA.getAttribute("href").slice(1) nextA.parentNode.classList.remove("hidden") setMoveMode(false) })) const shakeEvent = new Shake() shakeEvent.start() const clearScr = () => { pause() const tmp = music.splice(0, music.length) tmp.forEach(beep => beep.dispose()) rewind() scrollY = 0 borders.l = 0 borders.r = borderExtend setViewBox() } window.addEventListener('shake', clearScr) $('#clrBtn').addEventListener('click', clearScr) window.addEventListener('orientationchange', () => { touchNoises.splice(0,music.length).forEach(beep => beep.dispose()) }) window.addEventListener('blur', () => { masterVolume.gain.value = 0.0 }) window.addEventListener('focus', () => { masterVolume.gain.value = 0.5 }) const getSurroundingViewBox = () => { let t = Infinity, l = borders.l let b = -Infinity, r = borders.r music.forEach(m => m.coords.forEach(p => { t = min(t, p[1]) b = max(b, p[1]) })) if (!isFinite(t)) t = 0 if (!isFinite(b)) b = innerHeight return `${t} ${l} ${b-t+1} ${r-l+1}` } $`#dlBtn`.addEventListener('click', () => { const anchor = document.createElement("a") anchor.setAttribute("download", "awesome-music.svg") const viewBox = getSurroundingViewBox() const code = `\n`+ `\n` + `
\n${music.map(m => m.element.outerHTML).join("\n")}\n
` anchor.setAttribute("href", "data:application/octet-stream;base64,"+btoa(code)) anchor.click() }) $`#ulBtn`.addEventListener('click',() => { if (music.length > 0) { if (!confirm('Discard your current music ?')) return clearScr() } $`#inputFile`.click() }) $`#inputFile`.addEventListener('change', (e) => { const file = $`#inputFile`.files[0] const reader = new FileReader() reader.onload = () => { let code = reader.result.replace(/\<\?xml.+\?\>|\<\!DOCTYPE.+]\>/ig, '').trim() if (!code.slice(4) === "
{ const n = Noise.fromPath(p) if (n) music.push(n) }) $`#inputFile`.value="" setViewBox() } reader.readAsText(file) }) WebMidi.enable(function(err) { if (err) { console.log("No midi. Too bad :(") } WebMidi.inputs.forEach(input => { input.addListener('noteon', 'all', e => { const noteName = e.note.name + e.note.octave const f = noteToFreq(e.note.name, e.note.octave) const y = freqToY(f) if (!midiNoises[noteName]) { midiNoises[noteName] = new Noise({relX: cursorX, relY: y}, null, currentType) midiNoises[noteName].add({relX: cursorX + 5, relY: y}, null) scrollToCursor() } }) input.addListener('noteoff', 'all', e => { const noteName = e.note.name + e.note.octave const f = noteToFreq(e.note.name, e.note.octave) const y = freqToY(f) if (midiNoises[noteName]) { midiNoises[noteName].coords[1][0] = cursorX music.push(midiNoises[noteName]) midiNoises[noteName].mute() delete midiNoises[noteName] scrollToCursor() } }) }) }) const keyboardMappings = () => { const lang = navigator.language.slice(0,2) const mappings = { "de": "ysxdcvgbhnjmq2w3er5t6z7u", "en": "zsxdcvgbhnjmq2w3er5t6y7u", "fr": "wsxdcvgbhnj,a2z3er5t6y7u", } return mappings[lang]||mappings.en } window.addEventListener("keydown", (e) => { const mappings = keyboardMappings() console.log(e.key) const i = mappings.indexOf(e.key) if (i > -1) { const note = notes[i % 12] const oct = 1 + (i / 12)|0 const noteName = "_" + note + oct const f = noteToFreq(note, oct) const y = freqToY(f) if (!midiNoises[noteName]) { midiNoises[noteName] = new Noise({relX: cursorX, relY: y}, null, currentType) midiNoises[noteName].add({relX: cursorX + 5, relY: y}, null) scrollToCursor() } } }) window.addEventListener("keyup", (e) => { const mappings = keyboardMappings() const i = mappings.indexOf(e.key) if (i > -1) { const note = notes[i % 12] const oct = 1 + (i / 12)|0 const noteName = "_" + note + oct if (midiNoises[noteName]) { midiNoises[noteName].coords[1][0] = cursorX music.push(midiNoises[noteName]) midiNoises[noteName].mute() delete midiNoises[noteName] scrollToCursor() } } }) ~function loop() { const midiKeys = Object.keys(midiNoises) if (playing || midiKeys.length > 0) { cursorX+=2 if (midiKeys.length > 0 && cursorX > borders.r) { borders.r += borderExtend } midiKeys.forEach(k => { let n = midiNoises[k] if (n.coords.length == 2) { n.coords[1][0] = cursorX n.render() } }) scrollToCursor() setViewBox() } if (playing) { if (cursorX > borders.r && (!userIsJamming())) setCursor(borders.l) music.forEach(beep => beep.playAtX(cursorX)) } if (touchNoises.length > 0 || mouseNoise) { if (!playing) scrollX+=3 if (mouseNoise) { mouseNoise.add(mouseNoise.lastX, mouseNoise.lastY) } touchNoises.forEach(n => n.noise.add(n.noise.lastX,n.noise.lastY)) setViewBox() } requestAnimationFrame(loop) }(0)
粒子
时间
文字
hover
canvas
3d
游戏
音乐
火焰
水波
轮播图
鼠标跟随
动画
css
加载动画
导航
菜单
按钮
滑块
tab
弹出层
统计图
svg
×
Close
在线代码下载提示
开通在线代码永久免费下载,需支付20jQ币
开通后,在线代码模块中所有代码可终身免费下!
您已开通在线代码永久免费下载,关闭提示框后,点下载代码可直接下载!
您已经开通过在线代码永久免费下载
对不起,您的jQ币不足!可通过发布资源 或
直接充值获取jQ币
取消
开通下载
<!doctype html> <html> <head> <meta charset="utf-8"> <title>音乐油漆-jq22.com</title> <script src="https://www.jq22.com/jquery/jquery-1.10.2.js"></script> <style>
</style> </head> <body>
<script>
</script>
</body> </html>
2012-2021 jQuery插件库版权所有
jquery插件
|
jq22工具库
|
网页技术
|
广告合作
|
在线反馈
|
版权声明
沪ICP备13043785号-1
浙公网安备 33041102000314号