GitHub 首頁 地球儀 技術大揭秘
GitHub是世界構(gòu)建軟件的地方。全世界有5600多萬開發(fā)者在GitHub上共同開發(fā)和工作。通過我們的新主頁,我們希望展示開源開發(fā)如何超越我們生活的邊界,并通過一個開發(fā)者的旅程來講述我們的產(chǎn)品故事。
在2019年的Satellite上,我們的首席執(zhí)行官Nat展示了30天內(nèi)GitHub上開源活動的可視化。絕對的數(shù)量和全球影響力是驚人的,我們知道我們想在這個故事的基礎上再接再厲。
我們在設計和發(fā)展全球化的過程中要達到的主要目標是:
一個互聯(lián)的社區(qū). 我們探索了許多不同的方案,但最終還是選擇了拉取請求上。結(jié)果是一個美麗的可視化的拉動請求在世界的一個地方被打開,在另一個地方被關閉。
一個展示真實工作的窗口。我們一開始只是簡單地展示了拉動請求的弧線和塔頂,但很快意識到我們需要 "生命的證明"。這些弧線很可能只是設計動畫,而不是真實的工作。我們反復研究了提供更多細節(jié)的方法,發(fā)現(xiàn)最能引起共鳴的是清晰的懸停狀態(tài),顯示拉取請求、repo、時間戳、語言和位置。Nat提出了讓每一行都可以點擊的想法,這確實提升了體驗的層次,讓它更有沉浸感。
對細節(jié)和性能的關注。對我們來說,地球儀不僅要看起來鼓舞人心、美麗動人,而且要在所有設備上表現(xiàn)良好,這一點非常重要。我們經(jīng)歷了很多很多的迭代完善,還有更多的工作要做。
用WebGL渲染地球儀
在最基本的層面上,地球儀運行在一個由three.js驅(qū)動的WebGL上下文中。我們通過一個JSON文件向它提供最近在世界各地創(chuàng)建和合并的拉取請求數(shù)據(jù)。場景由五個圖層組成:一個光環(huán)、一個地球儀、地球的各個區(qū)域,藍色尖峰代表開放的拉取請求,粉色弧線代表合并的拉取請求。我們沒有使用任何紋理:我們將四盞燈指向一個球體,使用大約12000個五邊形圓圈來渲染地球的區(qū)域,并在球體的背面用簡單的自定義著色器繪制一個光環(huán)。
為了繪制地球的區(qū)域,我們首先定義所需的圓的密度(這將取決于您的機器的性能--稍后將詳細介紹),然后在一個嵌套的 for 循環(huán)中沿經(jīng)度和緯度循環(huán)。我們從南極開始向上,計算每個緯度的周長,沿著這條線均勻地分布圓圈,環(huán)繞球體。
for (let lat = -90; lat <= 90; lat += 180/rows) {
const radius = Math.cos(Math.abs(lat) * DEG2RAD) * GLOBE_RADIUS;
const circumference = radius * Math.PI * 2;
const dotsForLat = circumference * dotDensity;
for (let x = 0; x < dotsForLat; x++) {
const long = -180 + x*360/dotsForLat;
if (!this.visibilityForCoordinate(long, lat)) continue;
// Setup and save circle matrix data
}
}
為了確定一個圓是否應該可見(是水還是陸地?),我們加載一個包含世界地圖的小PNG,通過canvas的context.getImageData()解析其圖像數(shù)據(jù),并通過visibilityForCoordinate(long, lat)方法將每個圓映射到地圖上的一個像素。如果該像素的alpha值至少是90(255中的),則畫出這個圓;如果不是,我們就跳到下一個。
在收集了所有我們需要的數(shù)據(jù),通過這些小圓形可視化地球的區(qū)域之后,我們創(chuàng)建一個CircleBufferGeometry的實例,并使用InstancedMesh來渲染所有的幾何圖形。
確保你能看到自己的位置
當你進入新的GitHub主頁時,我們希望確保你能在地球儀出現(xiàn)時看到自己的位置,這意味著我們需要計算出你在地球上的位置。我們想在不延遲IP查找后的第一次渲染的情況下實現(xiàn)這個效果,所以我們將地球儀的起始角度設置為格林威治上空的中心,查看設備的時區(qū)偏移量,并將這個偏移量轉(zhuǎn)換為圍繞地球儀自身軸線的旋轉(zhuǎn)(單位:弧度)。
const date = new Date();
const timeZoneOffset = date.getTimezoneOffset() || 0;
const timeZoneMaxOffset = 60*12;
rotationOffset.y = ROTATION_OFFSET.y + Math.PI * (timeZoneOffset / timeZoneMaxOffset);
這不是一個精確的測量你的位置,但它是快速的,并做工作。
可視化拉取請求
當然,地球儀的主要行為是可視化世界各地正在打開和合并的所有拉請求。讓這一切成為可能的數(shù)據(jù)工程本身就是一個不同的話題,我們將在下一篇文章中分享我們?nèi)绾螌崿F(xiàn)這一點。在這里,我們想給你一個概述,我們是如何可視化你所有的拉請求的。
讓我們關注一下被合并的拉請求(粉色的弧線),因為它們更有趣一些。每個合并的拉請求條目都有兩個位置:打開的位置和合并的位置。我們將這些位置映射到我們的地球儀上,并在這兩個位置之間畫一條貝塞爾曲線。
const curve = new CubicBezierCurve3(startLocation, ctrl1, ctrl2, endLocation);
我們?yōu)檫@些曲線設置了三個不同的軌道,兩點之間的距離越長,我們就會把任何特定的弧線拉出更遠的空間。然后,使用TubeBufferGeometry的實例沿著這些路徑生成幾何體,這樣我們就可以使用setDrawRange()在線條出現(xiàn)和消失時對它們進行動畫處理。
當每條線動畫進來并到達其合并位置時,會在一個實心圓中生成和動畫,該圓在線存在時保持不變,而一個環(huán)則放大并立即淡出。這些動畫的漸進漸出是通過將一個速度(這里是0.06)與目標(1)和當前值(animated.dot.scale.x)之間的差值相乘,并將其加到現(xiàn)有的比例值上而產(chǎn)生的。換句話說,我們每走近一幀,就會向目標靠近6%,當我們離目標越來越近時,動畫就會自然而然地慢下來。
// The solid circle
const scale = animated.dot.scale.x + (1 - animated.dot.scale.x) * 0.06;
animated.dot.scale.set(scale, scale, 1);
// The landing effect that fades out
const scaleUpFade = animated.dotFade.scale.x + (1 - animated.dotFade.scale.x) * 0.06;
animated.dotFade.scale.set(scaleUpFade, scaleUpFade, 1);
animated.dotFade.material.opacity = 1 - scaleUpFade;
性能優(yōu)化帶來的創(chuàng)新約束
主頁和地球儀需要在各種設備和平臺上有良好的表現(xiàn),這在早期給我們帶來了一些創(chuàng)意上的限制,使我們廣泛關注于創(chuàng)建一個優(yōu)化良好的頁面。雖然一些現(xiàn)代電腦和平板電腦在開啟抗鋸齒功能的情況下可以以60 FPS的速度渲染地球儀,但并不是所有設備都能做到這一點,很早就決定關閉抗鋸齒功能,優(yōu)化性能。這樣一來,當?shù)厍騼x的高亮邊緣與背景的深色相接時,地球儀的左上角就會出現(xiàn)一條尖銳而像素化的線條。
這促使我們探索一種能夠隱藏像素化邊緣的光暈效果。我們通過使用自定義著色器在一個比地球儀稍大的球體背面繪制一個漸變效果,將其放置在地球儀的后面,并將其稍稍傾斜,以強調(diào)左上角的效果。
const halo = new Mesh(haloGeometry, haloMaterial);
halo.scale.multiplyScalar(1.15);
halo.rotateX(Math.PI*0.03);
halo.rotateY(Math.PI*0.03);
this.haloContainer.add(halo);
這樣做可以撫平尖銳的邊緣,同時比開啟抗鋸齒更有性能。不幸的是,關閉抗鋸齒也產(chǎn)生了相當突出的莫瑞效果,因為所有組成世界的圓圈在接近地球邊緣時,彼此越來越近。我們降低了這一效果,并通過對圓圈使用碎片著色器來模擬更濃厚的大氣,其中每個圓圈的alpha是其與攝像機距離的函數(shù),隨著圓圈的進一步移動,每個圓圈都會逐漸消失。
if (gl_FragCoord.z > fadeThreshold) {
gl_FragColor.a = 1.0 + (fadeThreshold - gl_FragCoord.z ) * alphaFallOff;
}
提高感知速度
我們不知道地球儀在特定設備上的加載速度有多快(或多慢),但希望確保主頁上的頭部構(gòu)成是平衡的,而且即使在我們渲染第一幀之前有一點延遲,你也會覺得地球儀加載得很快。
我們在Figma中只使用漸變創(chuàng)建了一個裸版的地球儀,并將其導出為SVG。將這個SVG嵌入到HTML文檔中,增加了很少的開銷,但可以確保在頁面加載時一些東西是立即可見的。一旦我們準備好渲染地球儀的第一幀,我們就會使用Web Animations API在SVG和畫布元素之間進行交叉漸變,并放大這兩個元素。使用Web Animations API可以讓我們在過渡過程中完全不接觸DOM,確保它盡可能的無停頓。
const keyframesIn = [
{ opacity: 0, transform: 'scale(0.8)' },
{ opacity: 1, transform: 'scale(1)' }
];
const keyframesOut = [
{ opacity: 1, transform: 'scale(0.8)' },
{ opacity: 0, transform: 'scale(1)' }
];
const options = { fill: 'both', duration: 600, easing: 'ease' };
this.renderer.domElement.animate(keyframesIn, options);
const placeHolderAnim = placeholder.animate(keyframesOut, options);
placeHolderAnim.addEventListener('finish', () => {
placeholder.remove();
});
質(zhì)量層級的優(yōu)雅降級
我們的目標是在保持60 FPS的同時,盡可能地渲染出一個美麗的地球儀,但要找到這個平衡點是很困難的--有成千上萬的設備,它們的性能都因運行的瀏覽器和心情而不同。我們不斷地監(jiān)控所達到的FPS,如果我們不能在最后50幀保持55.5 FPS,我們就會開始降低場景的質(zhì)量。
有四個質(zhì)量層級,每降低一個質(zhì)量層級,我們就會減少昂貴的計算量。這包括降低像素密度、我們的射線廣播頻率(弄清楚你的光標在場景中懸停的內(nèi)容),以及在屏幕上繪制的幾何體數(shù)量--這又讓我們回到了構(gòu)成地球區(qū)域的圓圈。當我們沿著質(zhì)量層向下移動時,我們會降低所需的圓圈密度,并重建地球的區(qū)域,這里從原來的約12000個圓圈變成了約8000個。
// Reduce pixel density to 1.5 (down from 2.0)
this.renderer.setPixelRatio(Math.min(AppProps.pixelRatio, 1.5));
// Reduce the amount of PRs visualized at any given time
this.indexIncrementSpeed = VISIBLE_INCREMENT_SPEED / 3 * 2;
// Raycast less often (wait for 4 additional frames)
this.raycastTrigger = RAYCAST_TRIGGER + 4;
// Draw less geometry for the Earth’s regions
this.worldDotDensity = WORLD_DOT_DENSITY * 0.65;
// Remove the world
this.resetWorldMap();
// Generate world anew from new settings
this.buildWorldGeometry();
廣泛努力的一小部分。
這些是我們用來渲染地球儀的一些技術,但地球儀和新主頁的創(chuàng)建是一個更長的故事的一部分,跨越多個團隊、學科和部門,包括設計、品牌、工程、產(chǎn)品和通信。
關于本文
譯者:@飄飄
作者:@Tobias Ahlin
原文:https://github.blog/2020-12-21-how-we-built-the-github-globe/
作者:@Tobias Ahlin
歡迎關注微信公眾號 :前端開發(fā)愛好者
添加好友備注【進階學習】拉你進技術交流群