特效:遞歸分形與粒子系統(tǒng)實(shí)戰(zhàn))
簡(jiǎn)介這是一份基于HTML5 Canvas的櫻花樹(shù)木動(dòng)態(tài)特效源碼面向前端初學(xué)者與網(wǎng)頁(yè)視覺(jué)特效愛(ài)好者幫助理解Canvas圖形繪制與粒子動(dòng)畫(huà)的實(shí)現(xiàn)思路。壓縮包共6個(gè)文件約3KB包含html頁(yè)面、js腳本、css樣式、使用幫助txt及兩個(gè)url鏈接分別承擔(dān)頁(yè)面結(jié)構(gòu)、動(dòng)畫(huà)邏輯、樣式控制與使用指引。代碼完整演示了樹(shù)干與樹(shù)枝的路徑繪制、花瓣的arc填充、飄落粒子系統(tǒng)、requestAnimationFrame動(dòng)畫(huà)循環(huán)以及鼠標(biāo)交互與性能優(yōu)化等關(guān)鍵環(huán)節(jié)讀者可據(jù)此掌握隨機(jī)分支生成、粒子重置與clearRect局部重繪等技巧。目前已有5436人學(xué)習(xí)下載適合作為Canvas入門練手與網(wǎng)頁(yè)浪漫背景特效的參考案例。1. 從一張靜態(tài)頁(yè)面到滿屏落櫻HTML5 Canvas 櫻花樹(shù)木特效到底在做什么很多人第一次看到「HTML5 Canvas 櫻花樹(shù)木特效代碼」這類需求腦子里浮現(xiàn)的是某個(gè)現(xiàn)成的 JS 插件復(fù)制一段 script 標(biāo)簽就能跑。實(shí)際做過(guò)就知道真正讓頁(yè)面「活」起來(lái)的不是某個(gè)庫(kù)而是 Canvas 2D 的繪制循環(huán)加上一套自己寫(xiě)的粒子系統(tǒng)。櫻花樹(shù)是靜態(tài)骨架飄落的花瓣是動(dòng)態(tài)粒子兩者疊在同一張畫(huà)布上靠 requestAnimationFrame 每幀重繪。這套東西能解決的需求很具體給個(gè)人主頁(yè)、節(jié)日活動(dòng)頁(yè)、產(chǎn)品落地頁(yè)加一層不依賴視頻、不依賴 GIF 的輕量氛圍動(dòng)效文件體積小、可調(diào)參數(shù)多、移動(dòng)端也能跑。適合誰(shuí)會(huì)一點(diǎn) JavaScript、能看懂 canvas 繪圖 API、想自己掌控動(dòng)畫(huà)節(jié)奏而不是被插件綁架的前端。下面從零把這棵樹(shù)和這場(chǎng)花瓣雨拆開(kāi)講清楚。2. 櫻花樹(shù)怎么畫(huà)Canvas 2D 遞歸分形與花瓣粒子的建模思路2.1 為什么用遞歸分形畫(huà)樹(shù)而不是貼一張 PNG最省事的做法當(dāng)然是讓設(shè)計(jì)師出一張櫻花樹(shù) PNG往 canvas 上一 drawImage 完事。但這樣做的代價(jià)是樹(shù)的大小固定、縮放會(huì)糊、想改枝干角度得重新出圖而且和飄落花瓣的層次關(guān)系很難做自然。用遞歸分形fractal tree畫(huà)樹(shù)本質(zhì)是把一根枝干當(dāng)成一條線段從起點(diǎn)按某個(gè)角度分叉出兩根更短、更細(xì)的子枝干遞歸到深度耗盡為止。這樣樹(shù)是「算」出來(lái)的不是「貼」出來(lái)的改一個(gè)角度參數(shù)整棵樹(shù)的形態(tài)就變了縮放不失真還能讓花瓣從真實(shí)的枝干末端位置生成而不是隨機(jī)撒在屏幕上方。遞歸分形的核心參數(shù)只有四個(gè)初始枝干長(zhǎng)度、每層長(zhǎng)度衰減系數(shù)、分叉角度、遞歸深度。深度每加一層枝干數(shù)量翻倍深度 10 就是 2 的 10 次方量級(jí)的分支所以深度一般控制在 9 到 12 之間再多瀏覽器單幀計(jì)算就吃力了。角度決定樹(shù)的「張開(kāi)程度」0.4 到 0.6 弧度之間比較像真實(shí)的櫻花樹(shù)太小像掃把太大像爆炸。// 遞歸繪制枝干x,y 起點(diǎn)len 長(zhǎng)度angle 角度depth 剩余深度 function drawBranch(ctx, x, y, len, angle, depth) { if (depth 0) return; // 計(jì)算枝干終點(diǎn)坐標(biāo) const x2 x Math.cos(angle) * len; const y2 y Math.sin(angle) * len; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x2, y2); // 枝干越細(xì)越淺模擬木質(zhì)漸變 ctx.lineWidth depth * 0.6; ctx.strokeStyle rgba(90, 60, 50, ${0.3 depth * 0.06}); ctx.stroke(); // 遞歸出左右兩根子枝干長(zhǎng)度衰減 0.72角度各偏 0.5 弧度 drawBranch(ctx, x2, y2, len * 0.72, angle - 0.5, depth - 1); drawBranch(ctx, x2, y2, len * 0.72, angle 0.5, depth - 1); }這段代碼里len * 0.72是長(zhǎng)度衰減0.72 意味著每往上一層枝干縮短 28%這個(gè)值低于 0.6 樹(shù)會(huì)顯得矮胖高于 0.8 會(huì)顯得瘦長(zhǎng)。angle ± 0.5是分叉角左右對(duì)稱。lineWidth depth * 0.6讓靠近根部的枝干粗、末梢細(xì)視覺(jué)上更像真樹(shù)。strokeStyle的 alpha 隨深度增加末梢更淡避免整棵樹(shù)糊成一團(tuán)黑。遞歸的終止條件是depth 0也就是畫(huà)到第 10 層左右就停。2.2 花瓣粒子系統(tǒng)位置、速度、旋轉(zhuǎn)、透明度的四要素樹(shù)畫(huà)完了是靜態(tài)的櫻花特效的靈魂在飄落的花瓣。花瓣不用真的畫(huà)一朵五瓣花用一個(gè)小橢圓或者一段二次貝塞爾曲線就夠因?yàn)樗谄聊簧贤ǔV挥袔紫袼氐绞畮紫袼丶?xì)節(jié)看不出來(lái)。每片花瓣是一個(gè)對(duì)象至少要有這幾個(gè)屬性x、y 當(dāng)前位置vx、vy 速度分量rotation 當(dāng)前旋轉(zhuǎn)角vr 旋轉(zhuǎn)角速度size 大小alpha 透明度。每幀更新時(shí) x vxy vyrotation vr當(dāng) y 超出畫(huà)布高度就重置回頂部形成循環(huán)?;ò甑倪\(yùn)動(dòng)不能是純勻速直線下落那樣像下雨不像花瓣。真實(shí)花瓣下落時(shí)左右搖擺所以給 vx 加一個(gè)基于正弦的擾動(dòng)讓水平方向來(lái)回飄。透明度也可以隨下落位置變化靠近底部稍微淡出避免花瓣在底部堆積得太突兀。// 初始化 80 片花瓣 const petals []; for (let i 0; i 80; i) { petals.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vy: 0.5 Math.random() * 1.5, // 下落速度 0.5~2 vx: -0.5 Math.random(), // 水平初速度 rotation: Math.random() * Math.PI * 2, vr: -0.02 Math.random() * 0.04, // 旋轉(zhuǎn)角速度 size: 4 Math.random() * 6, // 花瓣尺寸 4~10 alpha: 0.5 Math.random() * 0.5, phase: Math.random() * Math.PI * 2 // 搖擺相位讓每片花瓣不同步 }); }vy范圍 0.5 到 2 是下落速度太快像冰雹太慢像懸浮。vx給一個(gè) -0.5 到 0.5 的初值配合后面的正弦擾動(dòng)。phase是關(guān)鍵如果所有花瓣用同一個(gè)正弦函數(shù)它們會(huì)整齊劃一地左右擺看起來(lái)像機(jī)械波加了隨機(jī)相位后每片花瓣的搖擺節(jié)奏不同才有自然感。size和alpha的隨機(jī)范圍讓花瓣有遠(yuǎn)近層次大而實(shí)的看起來(lái)近小而淡的看起來(lái)遠(yuǎn)。2.3 主循環(huán)requestAnimationFrame 與每幀清屏的配合Canvas 動(dòng)畫(huà)的標(biāo)準(zhǔn)做法是每幀先 clearRect 清空整張畫(huà)布再重新畫(huà)樹(shù)和所有花瓣。不清屏的話上一幀的痕跡會(huì)殘留花瓣會(huì)拖出一條條尾巴。清屏用ctx.clearRect(0, 0, canvas.width, canvas.height)不要用 fillRect 填白色因?yàn)轫?yè)面背景可能是漸變色填白會(huì)蓋住背景。主循環(huán)用 requestAnimationFrame 而不是 setInterval原因是 rAF 會(huì)跟著瀏覽器刷新率走60Hz 屏幕就是 60 幀120Hz 就是 120 幀而且頁(yè)面切到后臺(tái)時(shí)會(huì)自動(dòng)暫停省電。setInterval 不管頁(yè)面可不可見(jiàn)都按固定間隔跑容易掉幀和浪費(fèi)性能。function loop() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 先畫(huà)樹(shù)背景層 drawBranch(ctx, canvas.width / 2, canvas.height, 120, -Math.PI / 2, 10); // 再畫(huà)花瓣前景層 petals.forEach(p { p.phase 0.02; p.x p.vx Math.sin(p.phase) * 0.8; // 正弦搖擺 p.y p.vy; p.rotation p.vr; // 超出底部就回到頂部隨機(jī)位置 if (p.y canvas.height 20) { p.y -20; p.x Math.random() * canvas.width; } ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation); ctx.globalAlpha p.alpha; ctx.fillStyle #ffb7c5; ctx.beginPath(); ctx.ellipse(0, 0, p.size, p.size * 0.6, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); requestAnimationFrame(loop); }Math.sin(p.phase) * 0.8是水平搖擺0.8 是搖擺幅度調(diào)大花瓣飄得更野調(diào)小更含蓄。ctx.save()和ctx.restore()成對(duì)出現(xiàn)保證 translate 和 rotate 不會(huì)污染下一片花瓣的坐標(biāo)系這是 Canvas 里最容易翻車的地方之一忘了 restore 會(huì)導(dǎo)致后面所有繪制都帶著上一片的位移和旋轉(zhuǎn)。ellipse畫(huà)橢圓花瓣半徑 x 是 sizey 是 size 的 0.6 倍壓扁一點(diǎn)更像花瓣形狀。3. 讓特效跑得穩(wěn)畫(huà)布適配、性能控制與移動(dòng)端兼容3.1 高分屏適配devicePixelRatio 與畫(huà)布尺寸的兩倍關(guān)系在 Retina 屏或者手機(jī)上直接設(shè) canvas 的 width/height 屬性等于 CSS 尺寸畫(huà)出來(lái)的東西會(huì)糊因?yàn)?canvas 的繪圖緩沖區(qū)默認(rèn)是 1 倍像素而屏幕物理像素是 2 倍甚至 3 倍。解決辦法是把 canvas 的 width/height 屬性設(shè)成 CSS 尺寸乘以 devicePixelRatio然后用 ctx.scale 把繪圖坐標(biāo)系縮回來(lái)。function resizeCanvas() { const dpr window.devicePixelRatio || 1; const rect canvas.getBoundingClientRect(); // 繪圖緩沖區(qū)按物理像素設(shè)置 canvas.width rect.width * dpr; canvas.height rect.height * dpr; // 坐標(biāo)系縮放回 CSS 像素這樣后續(xù)代碼不用改 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } window.addEventListener(resize, resizeCanvas); resizeCanvas();setTransform(dpr, 0, 0, dpr, 0, 0)等價(jià)于 scale(dpr, dpr)但用 setTransform 會(huì)重置之前的變換矩陣避免多次 resize 后縮放疊加。注意 resize 之后要重新計(jì)算花瓣的 x 范圍否則窗口變大后花瓣只集中在左側(cè)。這個(gè)坑我在一個(gè)活動(dòng)頁(yè)上踩過(guò)用戶把瀏覽器從半屏拖到全屏花瓣全擠在左邊三分之一排查了半天才發(fā)現(xiàn)是 resize 沒(méi)重置粒子位置。3.2 花瓣數(shù)量與幀率80 片和 300 片的性能分水嶺花瓣數(shù)量直接決定每幀的繪制調(diào)用次數(shù)。每片花瓣一次 save、一次 translate、一次 rotate、一次 ellipse、一次 fill、一次 restore80 片就是 480 次調(diào)用桌面瀏覽器輕松跑滿 60 幀。但到 300 片就是 1800 次調(diào)用中低端手機(jī)上會(huì)掉到 30 幀以下肉眼能看出卡頓。我的經(jīng)驗(yàn)值是桌面端 120 到 150 片移動(dòng)端 50 到 80 片用navigator.userAgent或者更靠譜的window.matchMedia((pointer: coarse))判斷觸屏設(shè)備來(lái)動(dòng)態(tài)調(diào)整。另外花瓣的 size 越大fill 的像素面積越大GPU 填充壓力也越大所以移動(dòng)端除了減數(shù)量還可以把 size 上限從 10 降到 7。設(shè)備類型花瓣數(shù)量size 范圍目標(biāo)幀率桌面端120~1504~1060fps移動(dòng)端50~803~730~60fps低端設(shè)備30~503~630fps如果還想再壓榨性能可以把花瓣預(yù)渲染到一張離屏 canvas 上主循環(huán)里只做 drawImage省掉每幀的路徑構(gòu)建。但離屏 canvas 會(huì)失去每片花瓣獨(dú)立旋轉(zhuǎn)的靈活性除非預(yù)渲染多個(gè)旋轉(zhuǎn)角度的貼圖復(fù)雜度上去了一般項(xiàng)目沒(méi)必要。3.3 頁(yè)面切后臺(tái)與內(nèi)存rAF 自動(dòng)暫停之外還要做什么requestAnimationFrame 在頁(yè)面切到后臺(tái)時(shí)會(huì)自動(dòng)暫停這是好事但有個(gè)細(xì)節(jié)切回來(lái)時(shí)p.phase這類累加變量不會(huì)跳變因?yàn)?rAF 不執(zhí)行就不累加所以動(dòng)畫(huà)會(huì)從暫停的地方繼續(xù)不會(huì)出現(xiàn)花瓣瞬移。真正要注意的是 resize 事件和 visibilitychange 事件的處理順序以及長(zhǎng)時(shí)間運(yùn)行后 petals 數(shù)組有沒(méi)有泄漏。如果頁(yè)面里有多個(gè) canvas 或者頻繁創(chuàng)建銷毀記得在組件卸載時(shí) cancelAnimationFrame把 rAF 的 id 存下來(lái)。我見(jiàn)過(guò)一個(gè)單頁(yè)應(yīng)用路由切換時(shí)舊頁(yè)面的 rAF 沒(méi)取消新頁(yè)面又起了一個(gè)兩個(gè)循環(huán)同時(shí)跑內(nèi)存和 CPU 都翻倍用戶反饋「越用越卡」就是這個(gè)原因。let rafId null; function start() { if (rafId) return; // 防止重復(fù)啟動(dòng) rafId requestAnimationFrame(loop); } function stop() { if (rafId) { cancelAnimationFrame(rafId); rafId null; } } // 頁(yè)面隱藏時(shí)停顯示時(shí)恢復(fù) document.addEventListener(visibilitychange, () { document.hidden ? stop() : start(); });if (rafId) return這個(gè)守衛(wèi)很關(guān)鍵防止多次調(diào)用 start 導(dǎo)致多個(gè)循環(huán)并行。visibilitychange 里手動(dòng) stop/start 其實(shí) rAF 自己也會(huì)暫停但顯式管理能保證狀態(tài)干凈尤其是配合上面的守衛(wèi)使用時(shí)。4. 避坑與排查櫻花特效最常見(jiàn)的 5 個(gè)翻車現(xiàn)場(chǎng)4.1 花瓣拖影忘了 clearRect 或者清屏區(qū)域不對(duì)現(xiàn)象是花瓣后面拖著一條半透明的尾巴像彗星。原因通常是主循環(huán)里沒(méi)調(diào) clearRect或者 clearRect 的寬高用了 CSS 尺寸而不是 canvas 的繪圖緩沖區(qū)尺寸。在做了 devicePixelRatio 適配之后canvas.width 是物理像素如果 clearRect 傳的是 CSS 尺寸右下角會(huì)有一塊區(qū)域清不干凈花瓣在那里越積越多。解決方法是 clearRect 統(tǒng)一用canvas.width和canvas.height因?yàn)?setTransform 已經(jīng)把坐標(biāo)系縮放到 CSS 像素了clearRect 傳物理像素值會(huì)被自動(dòng)縮放反而清多。最穩(wěn)妥的寫(xiě)法是ctx.clearRect(0, 0, canvas.width, canvas.height)配合 setTransform 使用。4.2 樹(shù)畫(huà)出來(lái)是歪的或者倒的角度單位搞混現(xiàn)象是枝干朝下長(zhǎng)或者朝右長(zhǎng)不是朝上。原因是 Canvas 的 y 軸向下為正角度 0 是朝右-Math.PI / 2才是朝上。很多人從數(shù)學(xué)坐標(biāo)系過(guò)來(lái)習(xí)慣性寫(xiě)Math.PI / 2以為朝上結(jié)果樹(shù)倒著長(zhǎng)。另外分叉角的正負(fù)也要注意angle - 0.5和angle 0.5一個(gè)偏左一個(gè)偏右如果兩個(gè)都寫(xiě)加號(hào)樹(shù)會(huì)一邊倒。解決方法是畫(huà)之前先在紙上標(biāo)一下起點(diǎn)在底部中央角度 -PI/2 朝上左分叉減角度右分叉加角度。4.3 移動(dòng)端花瓣卡頓數(shù)量沒(méi)降、size 沒(méi)減、還開(kāi)了陰影現(xiàn)象是桌面流暢手機(jī)上一頓一頓。除了花瓣數(shù)量還有一個(gè)隱藏殺手是ctx.shadowBlur。有人為了讓花瓣有發(fā)光效果加了陰影桌面看不出來(lái)移動(dòng)端 GPU 直接跪。陰影在 Canvas 里是逐像素計(jì)算的開(kāi)銷極大。解決方法是移動(dòng)端徹底去掉 shadowBlur用半透明的疊加橢圓模擬柔光或者干脆不加。另外ctx.globalCompositeOperation lighter也會(huì)增加混合開(kāi)銷移動(dòng)端慎用。4.4 花瓣在底部堆積重置邏輯只判了 y 沒(méi)判 x現(xiàn)象是花瓣落到屏幕底部后沒(méi)有回到頂部而是堆在底邊。原因是重置條件寫(xiě)成了if (p.y canvas.height)但花瓣的 x 可能已經(jīng)飄出屏幕左右兩側(cè)y 卻還沒(méi)到底于是永遠(yuǎn)不重置。或者重置時(shí)只改了 y 沒(méi)改 x花瓣全從同一個(gè) x 位置落下。解決方法是重置時(shí)同時(shí)隨機(jī) x并且判斷條件加上左右邊界if (p.y canvas.height 20 || p.x -50 || p.x canvas.width 50)超出任意一邊都回收。4.5 頁(yè)面縮放后畫(huà)布模糊只監(jiān)聽(tīng)了 resize 沒(méi)重設(shè) dpr現(xiàn)象是用戶按 Ctrl加號(hào)放大頁(yè)面或者把窗口拖到另一塊不同縮放比的顯示器上canvas 變糊。原因是 devicePixelRatio 變了但 resize 事件里沒(méi)重新讀取。瀏覽器縮放會(huì)觸發(fā) resize但 dpr 是動(dòng)態(tài)的必須在每次 resize 時(shí)重新讀window.devicePixelRatio。解決方法是把 dpr 的讀取放在 resizeCanvas 函數(shù)內(nèi)部而不是模塊頂層只讀一次。另外用window.matchMedia監(jiān)聽(tīng) dpr 變化更可靠但兼容性一般resize 里重讀已經(jīng)夠用。5. 進(jìn)階調(diào)參與復(fù)用把櫻花樹(shù)做成可配置的動(dòng)效組件5.1 用配置對(duì)象收攏所有魔法數(shù)字上面代碼里的 0.72、0.5、80、0.8 這些數(shù)字散落在各處改一個(gè)參數(shù)要翻半天。實(shí)際項(xiàng)目里我會(huì)把它們收進(jìn)一個(gè) config 對(duì)象初始化時(shí)合并默認(rèn)值這樣換一個(gè)頁(yè)面只改配置不改邏輯。const defaultConfig { branch: { length: 120, decay: 0.72, angle: 0.5, depth: 10 }, petal: { count: 100, minSize: 4, maxSize: 10, sway: 0.8, speed: [0.5, 2] }, color: { branch: 90,60,50, petal: #ffb7c5 } }; function createSakura(canvas, userConfig) { const cfg { ...defaultConfig, ...userConfig }; // 后續(xù)邏輯全部讀 cfg不再出現(xiàn)硬編碼 }{ ...defaultConfig, ...userConfig }是淺合并如果 userConfig 里只傳了petal.count其他 petal 字段會(huì)被整個(gè)覆蓋丟失。生產(chǎn)環(huán)境要用深合并或者約定配置必須傳完整子對(duì)象。我一般用 lodash 的 merge 或者自己寫(xiě)一個(gè)兩層的遞歸合并避免這個(gè)坑。5.2 花瓣形狀從橢圓升級(jí)到貝塞爾曲線橢圓花瓣看久了還是有點(diǎn)假想更像櫻花可以用二次貝塞爾畫(huà)一個(gè)帶缺口的瓣形。櫻花花瓣頂端有個(gè)小凹口用兩段貝塞爾能畫(huà)出來(lái)。function drawPetal(ctx, size) { ctx.beginPath(); ctx.moveTo(0, -size); // 右側(cè)弧線到頂端凹口 ctx.quadraticCurveTo(size * 0.8, -size * 0.3, size * 0.3, size * 0.2); // 凹口 ctx.quadraticCurveTo(0, 0, -size * 0.3, size * 0.2); // 左側(cè)弧線回起點(diǎn) ctx.quadraticCurveTo(-size * 0.8, -size * 0.3, 0, -size); ctx.fill(); }quadraticCurveTo的控制點(diǎn)決定弧度size * 0.8是控制點(diǎn)橫向偏移調(diào)大花瓣更圓調(diào)小更尖。中間那個(gè)quadraticCurveTo(0, 0, ...)就是頂端凹口控制點(diǎn)在原點(diǎn)讓曲線往里凹。這個(gè)形狀比橢圓多兩次路徑構(gòu)建性能開(kāi)銷增加約 30%花瓣數(shù)量要相應(yīng)下調(diào) 20% 左右。5.3 驗(yàn)證特效是否真的在跑用幀率計(jì)數(shù)和繪制調(diào)用數(shù)調(diào)完之后怎么確認(rèn)性能達(dá)標(biāo)我會(huì)在開(kāi)發(fā)階段加一個(gè)簡(jiǎn)易的 FPS 計(jì)數(shù)器每 60 幀統(tǒng)計(jì)一次平均幀間隔顯示在角落。另外用performance.now()包住主循環(huán)看單幀耗時(shí)有沒(méi)有超過(guò) 16.7ms60 幀的預(yù)算。如果單幀超過(guò) 16.7ms說(shuō)明這一幀的繪制量已經(jīng)撐不住 60 幀了要么減花瓣要么減深度。let lastTime performance.now(); let frames 0; let fps 0; function loop(now) { frames; if (now - lastTime 1000) { fps frames; frames 0; lastTime now; console.log(FPS:, fps); } // ... 繪制邏輯 requestAnimationFrame(loop); }這個(gè)計(jì)數(shù)器只在開(kāi)發(fā)時(shí)開(kāi)上線前注釋掉因?yàn)?console.log 本身也有開(kāi)銷。我習(xí)慣在 config 里加一個(gè)debug: false開(kāi)關(guān)控制是否輸出 FPS 和繪制調(diào)用數(shù)上線時(shí)關(guān)掉。最后說(shuō)個(gè)我自己的習(xí)慣每次做完這類 Canvas 動(dòng)效我都會(huì)在低端安卓機(jī)上真機(jī)跑一遍不是模擬器。模擬器的 GPU 和真機(jī)差太多模擬器 60 幀真機(jī)可能 25 幀?;ò陻?shù)量、size、陰影這三個(gè)參數(shù)是性能的命門調(diào)完必須真機(jī)驗(yàn)證。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取