svg dna解锁手机动画
代码语言:html
所属分类:动画
代码描述:svg dna解锁手机动画
代码标签: 手机动画
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body { margin: 0px; display: flex; justify-content: center; align-items: center; min-height: 100vh; width: 100vw; overflow: hidden; } .main-canvas { /* border: 1px dashed blueviolet; */ width: 200px; height: 350px; } .social-wrapper { opacity: 0.7; } .social-wrapper svg g path { stroke: rgb(177, 177, 177) !important; } .social-wrapper svg g rect { stroke: rgb(177, 177, 177) !important; } .social-wrapper svg g circle { stroke: rgb(177, 177, 177) !important; } .socials-container { padding: 4px; position: fixed; top: 0; left: 0; width: 20px; } </style> </head> <body translate="no"> <canvas class="main-canvas" width="1000" height="700"></canvas> <script> // svgs const fingerprintSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><title>fingerprint</title><g id="finger-print"><path d="M160.38,222.11s12,84.05-46.83,130.88" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M158,200.5s6-38.43,43.22-37.23,42,37.23,42,37.23,3.6,32.42,3.6,45.62" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M178.39,280.94s6.23-46.79,1.2-74.44c-4.8-26.42,37.22-31.22,40.82-6s20.41,90.05-42,175.3" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M200,205.3S225.21,293,155.57,372.2" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M204.8,378.2S242,321.77,245.63,266.54" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M131.56,365s33.62-30,40.82-62.43" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M94.34,342.18s30-25.22,39.62-54a140.18,140.18,0,0,0,6-62.44c-1.2-9.6-7.2-43.22,15.61-63.64s39.63-19.21,51.63-18" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M227.62,148.87s90.05,40.82,3.6,226.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M258.83,367.39s20.42-37.22,21.62-56.43" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M285.25,290.55s10.81-57.63-2.4-102.06C257.63,84,105.14,111.64,117.15,213.7c4.8,31.22,2.4,37.23,1.2,43.23" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M77.53,329s33.62-28.82,36-49.23" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M64.32,311s28.82-20.41,31.22-46.83S93,219,94.34,198.1c1.2-19.22,10.8-50.43,39.62-75.65" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M153.17,111.64s62.44-32.41,116.47,14.41" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M284.05,141.66s56.43,66,4.8,211.33" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M52.31,295.35s36-30,24-67.24c-4.81-10.8-3.6-19.21-3.6-19.21" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M77.53,188.49C107.55,36,310.46,46.81,327.27,199.3" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M329.68,222.11S336.88,287,318.87,329" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M242.76,65.48c71.31,21.73,130.11,96.81,107.33,222.67" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M42.71,276.14s16.81-12,12-44.43c-4.8-14.4-1.2-38.42-1.2-38.42,14.39-100.7,94.14-144,168-132.66" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M298.91,66.41A170.26,170.26,0,0,1,370.5,205.3c0,1.83,0,3.65-.09,5.47" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M61,106.55A170.64,170.64,0,0,1,276.52,52.89" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/><path d="M36,252.13A171.12,171.12,0,0,1,49,126.05" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:5px"/></g></svg>`; const phoneSvg = `<svg id="phone" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 683 1333"><title>phone-screen</title><path id="outer-path" d="M111.31,1309.27c-50.43,0-91.31-42.08-91.31-94L19.69,114.5c0-51.91,40.89-94,91.32-94H565c50.43,0,91.31,42.09,91.31,94L656,1215.27c0,51.92-40.89,94-91.32,94Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M619,115.5c0-.17,0-.33,0-.5,0-31.2-26-56.5-58-56.5H508s-7,1-10,4-3,4-3,10-9,32-30,32H212c-21,0-30-26-30-32s0-7-3-10-10-4-10-4H119c-32,0-58,25.3-58,56.5,0,.17,0,.33,0,.5v1102c0,.17,0,.33,0,.5,0,31.2,26,56.5,58,56.5H561c32,0,58-25.3,58-56.5,0-.17,0-.33,0-.5Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><circle cx="408" cy="73.5" r="11" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><rect x="300" y="68.5" width="80" height="11" rx="5.5" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></svg>`; const fingerASvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-a</title><g id="finger-a"><path d="M192.33,238.36s29.38,17.34,68.57,8.09" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M192.33,105.42s10.88,25.43,39.18,26.59S274,100.8,275,93.86s2.18-16.18,2.18-16.18S259.81,53.4,231.51,54.56s-37,20.8-37,20.8Z" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M158.58,475.34s55.51,1.15,71.84,13.87,35.92-13.87,35.92-13.87" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M188,423.32s23.95-1.16,31.57,15" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M255.45,461.47s-45.71-12.72-52.24-5.78" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M211.92,391s43.53,3.47,49,43.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M255,808.5l12.43-97.34s35.91-228.89,35.91-271.66S299,141.26,296.81,119.29,283.75,41.84,238,36.06s-55.51,34.68-57.69,55.49-17.41,209.23-18.5,230S140.08,433.72,139,460.31s-27.21,150.28-44.62,178" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const fingerBSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-b</title><g id="finger-b"><path d="M228.5,851.5s29-194,42-236,48-210,51-258,22-227,7-262-42-49-59-46-33,18-33,18l-32,134s-4,29-6,50-40,141-40,141-20,35-27,67-48,173-70,219" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M197.5,251.5s26,6,35,0" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M146.5,485.5s47,32,66,13" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M164.5,455.5s25,27,51,14" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M172.5,422.5s-23,20-19,45" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M242.5,62.57s37,.93,52,26.93l-5,23s-11,24-40,24S221,132.37,221,132.37" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const fingerCSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 418 865"><title>finger-c</title><g id="finger-c"><path d="M186.14,851.73S231.86,700,232.83,692.2,350.54,375.08,351.51,291.42c0,0,45.72-122.57,37-180.93s-34.05-60.31-34.05-60.31S321.36,40.45,312.6,55s-28.21,73-28.21,73-40.85,107-39.88,113.81c0,0-52.53,89.5-67.12,136.19,0,0-21.4,25.29-30.16,42.8s-13.62,39.88-13.62,43.77S45.09,666.91,28.55,682.47" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M138.48,469.44s20.42,19.45,54.47,8.75" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M160.85,446.09s10.7,11.67,23.35,11.67" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M172.52,404.26s-21.4,33.08-20.42,53.5" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M250.34,242.78s18.49-2.91,26.27,1.95" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/><path d="M284.39,128s49.61,1,64.2-41.83c0,0-9.83-24.74-36-27.93" style="fill:none;stroke:#000;stroke-miterlimit:10;stroke-width:4px"/></g></svg>`; const lockSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"><title>lock</title><g id="lock"><path d="M299,317c0,36-30,33-30,33H124c-26,0-25-33-25-33V217s-1-33,25-33H269s30-3,30,33"/><path d="M200,65a79,79,0,0,0-79,79v64h29V144a50,50,0,0,1,100,0V313h29V144A79,79,0,0,0,200,65Z"/></g></svg>`; // colors const lightColors = { bg: "white", outline: "black", fingerprint: "black" }; const darkColors = { bg: "black", outline: "white", fingerprint: "red" }; const canvas = document.querySelector(".main-canvas"); const canvas2 = document.createElement("canvas"); const ctx = canvas.getContext("2d"); const ctx2 = canvas2.getContext("2d"); function main() { let resp = computeRes(); const glitchManager = new GlitchManager(resp); let textManager = new TextManager(resp, ["Setup DNA Unlock"]); let stateIndex = 0; const createState = (id, time) => ({ id, time }); const states = [ createState("fadein", 2500), createState("1stinstruction", 500), createState("finger-hold", 2000), createState("finger-off", 500), createState("needles", 3600), createState("glitch-beginning", 250), createState("demonic-scrawl", 6000), createState("lock", 500), createState("unlock", 400), createState("again?", 4000)]; function onScreenChange() { Object.assign(resp, computeRes()); canvas.style.width = resp.styleSize.w + "px"; canvas.style.height = resp.styleSize.h + "px"; canvas.width = resp.rasterSize.w; canvas.height = resp.rasterSize.h; canvas2.width = resp.rasterSize.w; canvas2.height = resp.rasterSize.h; } onScreenChange(); window.onresize = onScreenChange; const phonePaths = svgStringToPathList(phoneSvg); const lockPaths = svgStringToPathList(lockSvg).map((x) => getPathPoints(x).map(toLV3)); const fingerprintPaths = svgStringToPathList(fingerprintSvg).map((x) => getPathPoints(x).map(toLV3)); const phoneFrontPoints = getPathPoints(phonePaths[0]).map(toLV3); const phoneBackPoints = getPathPoints(phonePaths[0]).map( x => new LV3(x.x, x.y, -30)); const phoneDisplayPoints = getPathPoints(phonePaths[1]).map(toLV3); const phoneCenter = getPhoneCenter(phoneFrontPoints); const fingerAnim = new FingerAnimator( fingerASvg, fingerBSvg, fingerCSvg, resp); const phoneParts = { phoneCenter, phoneBackPoints, phoneDisplayPoints, phoneFrontPoints }; let lines = Line.createInitialList(); const needles = Needle.createInitialList(); // lock colors const lockBlackColor = new LV3(0, 0, 0); const greenHex = hex2RGB("#17AB69"); const lockGreenColor = new LV3(greenHex.r, greenHex.g, greenHex.b); let time = 0; let prevDelta = 0; let lastBG = "white"; const body = document.body; function renderFunction(delta) { const dt = delta - prevDelta; time += dt; const state = states[stateIndex]; let colors = state.id === "demonic-scrawl" ? darkColors : lightColors; if (state.id === "glitch-beginning") { colors = Math.random() < 0.5 ? darkColors : lightColors; } if (colors.bg !== lastBG) { lastBG = colors.bg; body.style.backgroundColor = colors.bg; } const T = Math.min(time / state.time, 1); let firstCtx = ctx; if (state.id === "glitch-beginning" || state.id === "demonic-scrawl") { firstCtx = ctx2; } firstCtx.fillStyle = colors.bg; firstCtx.fillRect(0, 0, resp.rasterSize.w, resp.rasterSize.h); firstCtx.fillStyle = colors.outline; // firstCtx.font = '50px serif' // firstCtx.fillText(`${Math.floor(time)}`, 30, 60); // firstCtx.fillText(`${state.id}`, 30, 120); // firstCtx.fillText(`${T}`, 30, 180); // each state switch (state.id) { case "fadein": const clrs = Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${T})` }); drawPhone(resp, ctx, 0, clrs, phoneParts); textManager.draw(ctx, T, clrs); break; case "1stinstruction": drawPhone(resp, ctx, 0, colors, phoneParts); drawFinger(resp, ctx, T, colors, fingerAnim); textManager.draw(ctx, T, colors); break; case "lock": drawPhone(resp, ctx, 0, colors, phoneParts); drawLock(lockPaths, resp, ctx, 0, 0, "black"); break; case "unlock": { const t = Math.floor(T * 10) / 10; const lockColor = lockGreenColor.sub(lockBlackColor).scale(t); lockColor.x = Math.floor(lockColor.x); lockColor.y = Math.floor(lockColor.y); lockColor.z = Math.floor(lockColor.z); drawPhone(resp, ctx, 0, colors, phoneParts); drawLock( lockPaths, resp, ctx, t, 0, `rgb(${lockColor.x}, ${lockColor.y}, ${lockColor.z})`); } break; case "again?": drawPhone(resp, ctx, 0, colors, phoneParts); drawLock( lockPaths, resp, ctx, 1, 0, `rgb(${lockGreenColor.x}, ${lockGreenColor.y}, ${lockGreenColor.z})`); textManager.draw(ctx, T, colors); break; case "finger-hold": drawPhone(resp, ctx, 0, colors, phoneParts); drawFinger( resp, ctx, 1, Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${0.3})` }), fingerAnim); drawFingerprint(resp, ctx, colors, 0, fingerprintPaths); textManager.draw(ctx, T, colors); break; case "finger-off": drawPhone(resp, ctx, T * -60, colors, phoneParts); drawFinger( resp, ctx, 1 - T, Object.assign({}, colors, { outline: `rgba(0, 0, 0, ${0.3})` }), fingerAnim); drawFingerprint(resp, ctx, colors, T * -60, fingerprintPaths); drawNeedles(needles, resp, ctx, T * -60, dt); break; case "needles": drawPhone(resp, ctx, -60, colors, phoneParts); drawFingerprint( resp, ctx, Object.assign({}, colors, { fingerprint: `rgba(50, 50, 50, 0.3)` }), -60, fingerprintPaths); drawNeedles(needles, resp, ctx, -60, dt); drawFinger(resp, ctx, 0, colors, fingerAnim); textManager.draw(ctx, T, colors); break; case "glitch-beginning": glitchManager.N = 4; glitchManager.refreshRate = 200; glitchManager.tick(dt); drawPhone(resp, ctx2, -60, colors, phoneParts); drawFingerprint(resp, ctx2, colors, -60, fingerprintPaths); drawNeedles(needles, resp, ctx2, -60, dt); drawGlitchedScreen(glitchManager, resp, canvas2, ctx, colors); break; case "demonic-scrawl": glitchManager.N = 12; glitchManager.refreshRate = 100; glitchManager.tick(dt); const angle = -60 + T * 60; drawPhone(resp, ctx2, angle, colors, phoneParts); drawFingerprint(resp, ctx2, colors, angle, fingerprintPaths); drawNeedles(needles, resp, ctx2, angle, dt); drawLines(lines, resp, ctx2, angle, dt); textManager.draw(ctx2, T, colors); drawGlitchedScreen(glitchManager, resp, canvas2, ctx, colors); break;} prevDelta = delta; if (time > state.time) { stateIndex = (stateIndex + 1) % states.length; switch (states[stateIndex].id) { case "demonic-scrawl": lines = Line.createInitialList(); textManager.textList = [ "Your privacy is", "our priority"]; break; case "needles": textManager.textList = [`Your DNA is then`, "extracted and analysed"]; break; case "1stinstruction": case "finger-hold": textManager.textList = [`Place your finger`, "on the sensor"]; break; case "fadein": textManager.textList = ["Setup DNA Unlock"]; break; case "again?": textManager.textList = ["easy."]; break;} time = 0; } requestAnimationFrame(renderFunction); } requestAnimationFrame(renderFunction); } function drawPhone( resp, ctx, angle, colors, { phoneCenter, phoneBackPoints, phoneDisplayPoints, phoneFrontPoints }) { const mat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(3 * resp.anchor), LMat4.scale(0.2), LMat4.trans(-phoneCenter.x, -phoneCenter.y, 0)]); // back of phone const pp3a = phoneBackPoints.map(x => mat.multLV3(x)); ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; drawPath(ctx, pp3a, true); // front of phone const pp1a = phoneFrontPoints.map(x => mat.multLV3(x)); ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; fillPath(ctx, pp1a, true); drawPath(ctx, pp1a, true); // draw inner part of phone ctx.fillStyle = colors.bg; ctx.strokeStyle = colors.outline; const pp2a = phoneDisplayPoints.map(x => mat.multLV3(x)); drawPath(ctx, pp2a, true); } function drawFinger(resp, ctx, time2, colors, fingerAnimator) { const time = Math.max(0, Math.min(time2, 1)); const toFinger = t => { let finger = "b"; if (t <= 0.45) finger = "c";else if (t >= 0.95) finger = "a"; if (t <= 0.5) { const pa = fingerAnimator.getTranslations("c"); const pb = fingerAnimator.getTranslations("b"); const p = pb. sub(pa). scale(t / 0.5). add(pa); return [p, finger]; } else { const pa = fingerAnimator.getTranslations("b"); const pb = fingerAnimator.getTranslations("a"); const p = pb. sub(pa). scale((t - 0.5) / 0.5). add(pa); return [p, finger]; } }; const [p, fingerId] = toFinger(time); const fingerMat = buildMatrix([ LMat4.trans(p.x, p.y, 0), LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), LMat4.scale(0.6 * resp.anchor), LMat4.trans(-230, -135, 0)]); ctx.strokeStyle = colors.outline; fingerAnimator.getFinger(fingerId).forEach(a => { const b = a.map(x => fingerMat.multLV3(x)); drawPath(ctx, b, false); }); } function drawFingerprint(resp, ctx, colors, angle = 0, fingerprintPaths) { const fingerprintMatrix = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(0.23 * resp.anchor), LMat4.trans(-200, -200, 0)]); ctx.strokeStyle = colors.fingerprint; fingerprintPaths.forEach(a => { const b = a.map(x => fingerprintMatrix.multLV3(x)); drawPath(ctx, b, false); }); } function drawNeedles(needles, resp, ctx, angle, delta) { const needlesMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(resp.anchor * 0.6)]); needles.forEach(needle => { needle.draw(ctx, needlesMat); needle.tick(delta * 0.05); }); } function drawLines(lines, resp, ctx, angle, delta) { ctx.strokeStyle = "red"; const linesMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2, resp.rasterSize.h / 2, 0), getPhoneRotationMat(resp, angle), LMat4.scale(resp.anchor * 0.65)]); lines.forEach(l => l.draw(ctx, linesMat)); const newLines = []; lines.forEach(l => { if (l.isComplete() && !l.spawned) { newLines.push(...l.createChildren()); } else { l.tick(delta * 0.003); } }); lines.push(...newLines); } function drawLock(lockPaths, resp, ctx, time, angle = 0, color) { const xOffset = resp.anchor * 18; const scale = 0.14; const yox = 380; const lockMat = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2 + xOffset, yox * resp.anchor, 0), getPhoneRotationMat(resp, angle), LMat4.scale(scale * resp.anchor), LMat4.trans(-200, -200, 0)]); ctx.fillStyle = color; fillPath( ctx, lockPaths[0].map(x => lockMat.multLV3(x)), true); const lockMat2 = buildMatrix([ LMat4.trans(resp.rasterSize.w / 2 + xOffset, yox * resp.anchor, 0), getPhoneRotationMat(resp, angle), LMat4.scale(scale * resp.anchor), // rotate LMat4.trans(65, 0, 0), LMat4.rotateY(time * 180), LMat4.trans(-65, 0, 0), //end rotate LMat4.trans(-200, -200, 0)]); fillPath( ctx, lockPaths[1].map(x => lockMat2.multLV3(x)), true); } function drawGlitchedScreen(glitchManager, resp, canvasFrom, ctxTo, colors) { ctxTo.fillStyle = colors.bg; ctxTo.fillRect(0, 0, resp.rasterSize.w, resp.rasterSize.h); ctxTo.drawImage(canvasFrom, 0, 0, resp.rasterSize.w, resp.rasterSize.h); const cvWidth = resp.rasterSize.w; function createGlitch(ypos, sz = 2, N = 5) { if (ypos + N * 2 >= resp.rasterSize.h) return; for (let i = 0; i < N; i++) { const xCopyAmount = cvWidth - i * sz; ctxTo.drawImage( canvasFrom, 0, // x from ypos + i, // y from xCopyAmount, // copy width sz, // copy height i * sz, // x from ypos + i, // y from xCopyAmount, // copy over width sz // copy over height ); } const yp2 = N + ypos; for (let j = 0; j < N; j++) { const i = N - j; const xTo = N * sz - i * sz; const xCopyAmount = cvWidth - xTo; ctxTo.drawImage( canvasFrom, 0, // x from yp2 + i, // y from xCopyAmount, // copy width sz, xTo, // x to yp2 + i, // y to xCopyAmount, sz); } } glitchManager.glitches.forEach(g => createGlitch(g.ypos, g.sz, g.N)); } function getPathPoints(pathString) { const cp = compilePath(pathString); return computePoints(cp); } function getPhoneCenter(phoneFrontPoints) { let phoneCenter = new LV2(0, 0); const toLV2 = x => new LV2(x.x, x.y); let min = toLV2(phoneFrontPoints[0]), max = toLV2(phoneFrontPoints[0]); phoneFrontPoints.forEach(pp => { const p = toLV2(pp); min = minLV2(min, p); max = maxLV2(max, p); }); phoneCenter = max.sub(min).scale(0.5); phoneCenter.x = Math.floor(phoneCenter.x); phoneCenter.y = Math.floor(phoneCenter.y); return phoneCenter; } function circle(ctx, p, rad, color = "white") { ctx.strokeStyle = color; ctx.beginPath(); ctx.arc(p.x, p.y, rad, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); } function getPhoneRotationMat(resp, angle) { return buildMatrix([ LMat4.trans(-angle * resp.anchor * 2, angle * resp.anchor * 2, 0), LMat4.rotateY(angle), LMat4.rotateZ(-angle / 8)]); } function computeRes() { const width = window.innerWidth; const height = window.innerHeight; const currentResp = width / height; const res = 2 / 3; let styleSize = { w: 0, h: 0 }; if (currentResp >= res) { // landscape styleSize.h = Math.min(800, height - 2); styleSize.w = Math.floor(res * styleSize.h); } else { // portait styleSize.w = Math.min(450, width - 2); styleSize.h = Math.floor(styleSize.w / res); } const rasterSize = { w: styleSize.w * window.devicePixelRatio, h: styleSize.h * window.devicePixelRatio }; const anchor = rasterSize.w * 0.0012; return { styleSize, rasterSize, anchor }; } class Line { constructor(p, v, len, decendent = 0) { this.p = p; this.v = v; this.len = len; this.decendent = decendent; this.spawned = false; this.t = 0; } draw(ctx, t) { const end = this.p.add(this.v.scale(this.len * this.t)); const a = t.multLV3(this.p); const b = t.multLV3(end); ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); } tick(am = 0.2) { this.t += am; this.t = Math.min(1, this.t); } isComplete() { return this.t >= 1; } static choice(N) { return Math.floor(Math.random() * N); } createChildren() { if (this.decendent > 80) return []; // possibilities 0deg, -30deg, 30deg const numChildren = this.decendent < 3 ? Line.choice(3) + 1 : Line.choice(3) + Line.choice(3) - 1; const childrenMade = new Set(); const children = []; const end = this.p.add(this.v.scale(this.len)); for (let i = 0; i < numChildren; i++) { const deg = Line.choice(3); if (!childrenMade.has(deg)) { const p = end.copy(); let v = this.v.copy(); const angle = deg === 0 ? 0 : deg === 1 ? -30 : 30; const mat = LMat4.rotateZ(angle); v = mat.multLV3(v).unit(); const newLen = Math.max( this.len * (1 + (Math.random() * 0.25 - 0.1)), 10); if (Line.inPhoneDisplay(p)) { children.push(new Line(p, v, newLen, this.decendent + 1)); childrenMade.add(deg); } } } this.spawned = true; return children; } static inPhoneDisplay(p) { const mn = new LV2(-202, -458); const mx = new LV2(245, 548); return p.x >= mn.x && p.x <= mx.x && p.y >= mn.y && p.y <= mx.y; } static createInitialList() { const lines = []; const lineCenters = new LV3(0, 0, 0); const N = 25; const radius = 60; for (let i = 0; i < N; i++) { const angle = 360 * i / N + (Math.random() - 0.5) * 5; const vec = new LV3(Math.cos(angle / 57.3), Math.sin(angle / 57.3), 0); const p = lineCenters.add(vec.scale(radius)); const LL = new Line(p, vec.unit(), 20 + Math.random() * 7); lines.push(LL); } return lines; }} class FingerAnimator { constructor(fa, fb, fc, resp) { this.pa = svgStringToPathList(fa).map(x => getPathPoints(x).map(toLV3)); this.pb = svgStringToPathList(fb).map(x => getPathPoints(x).map(toLV3)); this.pc = svgStringToPathList(fc).map(x => getPathPoints(x).map(toLV3)); this.resp = resp; } getFinger(type) { switch (type) { case "c": return this.pc; case "b": return this.pb; case "a": default: return this.pa;} } getTranslations(type) { const resp = this.resp; switch (type) { case "b": return this.getTranslations("a").sub( new LV3(130 * resp.anchor, 90 * resp.anchor, 0)); case "c": return this.getTranslations("b").sub( new LV3(80 * resp.anchor, 80 * resp.anchor, 0)); case "a": default: return new LV2(0, 0);} }} class Needle { constructor(p, offsetPerc) { this.p = p; this.baseLen = 6; this.len = 120; this.bloodPercent = Math.random() * 0.3 + 0.15; // compute angle this.radians = Math.atan(this.baseLen * 0.5 / this.len); this.percentOut = 1; this.spd = Math.floor(50 + Math.random() * 40); this.t = Math.floor((offsetPerc || 0) * this.spd); } tick(am = 1) { this.t += am; const spd = this.spd; const hlv = spd * 0.5; const qt = spd * 0.25; this.t %= spd; if (this.t <= qt) { this.percentOut = this.t / qt; } else if (this.t <= hlv) { this.percentOut = 1 - (this.t - qt) / qt; } else { this.percentOut = 0; } // this.percentOut = (Math.sin(this.t * 0.2)+1) / 2; } computePoints() { const length = this.len * this.percentOut; const tip = this.p.add(new LV3(0, 0, length)); const b1 = this.p.add(new LV3(0, -length * Math.tan(this.radians), 0)); const b2 = this.p.add(new LV3(0, length * Math.tan(this.radians), 0)); let blood1, blood2; const bloodLength = this.len * this.bloodPercent; if (length >= this.bloodPercent * this.len) { blood1 = tip.add( new LV3( 0, -Math.sin(this.radians) * bloodLength, -Math.cos(this.radians) * bloodLength)); blood2 = tip.add( new LV3( 0, Math.sin(this.radians) * bloodLength, -Math.cos(this.radians) * bloodLength)); } return [this.p.copy(), b1, b2, tip, blood1, blood2].map( x => x && x.add(new LV3(0, 0, 0))); } draw(ctx, mat) { if (this.percentOut <= 0) return; const ptz = this.computePoints().map((x) => x ? mat.multLV3(x) : undefined); const v = ptz[3].sub(ptz[0]).unit(); if (v.dot(new LV3(0, 0, -1)) > 0.6) { return; } ctx.fillStyle = "#8C9995"; fillPath(ctx, ptz.slice(1, 4), true); if (ptz[4] && ptz[5]) { ctx.fillStyle = "red"; fillPath(ctx, ptz.slice(3, 6), "red"); } } static createInitialList() { const needles = []; const needlesGen = [ { rad: 20, N: 5 }, { rad: 30, N: 10 }, { rad: 40, N: 15 }]; needlesGen.forEach(({ rad, N }) => { for (let i = 0; i < N; i++) { const angle = i / N * 360; const vec = new LV3( Math.cos(angle / 57.3), Math.sin(angle / 57.3), 0). scale(rad); const n = new Needle(new LV3(vec.x, vec.y, 0), Math.random()); needles.push(n); } }); return needles; }} class GlitchManager { constructor(resp) { this.resp = resp; this.N = 10; this.glitches = []; this.t = 0; this.refreshRate = 200; this.refresh(); } tick(dt) { this.t += dt; if (this.t >= this.refreshRate) { this.refresh(); this.t -= this.refreshRate; } } refresh() { const resp = this.resp; const N = Math.random() * this.N; const newG = []; for (let i = 0; i < N; i++) { newG.push({ ypos: Math.floor(Math.random() * (resp.rasterSize.h - 1)), N: Math.floor( Math.floor(Math.random() * 5 * resp.anchor) * 2 + 8 * resp.anchor), sz: Math.max( Math.floor(Math.random() * 3 * resp.anchor + 2 * resp.anchor), 1) }); } this.glitches = newG; }} class TextManager { constructor(resp, textList, multiline = true) { this.resp = resp; this.textList = textList; this.textIndex = 0; this.multiline = multiline; } draw(ctx, T, colors) { if (this.textList.length === 0) return; const resp = this.resp; const text = this.getText(T); if (!text) return; this.setupDraw(ctx, colors); if (!this.multiline) { ctx.fillText( text, resp.rasterSize.w / 2 - resp.anchor * text.length * 7.8, resp.rasterSize.h - resp.anchor * 120); } else { for (let i = 0; i < text.length; i++) { const t = text[i]; ctx.fillText( t, resp.rasterSize.w / 2 - resp.anchor * t.length * 7.8, resp.rasterSize.h - resp.anchor * 120 + resp.anchor * i * 42); } } } getText(T) { if (this.multiline) { return this.textList; } return this.textList[Math.floor(T * this.textList.length)]; } setupDraw(ctx, colors) { const resp = this.resp; ctx.fillStyle = colors.outline; ctx.font = `${ 36 * resp.anchor }px SF Pro Display,SF Pro Icons,Helvetica Neue,Helvetica,Arial,sans-serif`; }} const BZC = { /** * @param {number} n * @param {number} i */ nChooseI(n, i) { return this.factorial(n) / (this.factorial(i).........完整代码请登录后点击上方下载按钮下载查看
网友评论0