מבקר פותח דף עם מפה, והדפדפן מתחיל לגמגם: הגלילה קופצת, תוויות מופיעות אחרי שנייה. אם נתקלתם בזה — הנתונים גדולים מדי עבור SVG או Canvas 2D. WebGL פותר את הבעיה הזו. הוא מציג מאות אלפי נקודות בקריאת ציור אחת, תוך שימוש ב-GPU של הלקוח. עם 500,000+ נקודות, Canvas 2D יורד ל-3 פריימים לשנייה, בעוד WebGL שומר על 60 פריימים לשנייה. זה לא רק שיפור. זו יכולת ליצור דשבורדים בזמן אמת שלא היו אפשריים קודם. עלות פרויקט טיפוסית נעה בין $2,000 ל-$10,000 בהתאם למורכבות.
פעם עבדנו על פרויקט ויזואליזציה של נתונים גיאוגרפיים עבור חברת לוגיסטיקה. היו להם 800 אלף נקודות עם קואורדינטות ומשקלים. Canvas 2D סיפק 5 פריימים לשנייה. שכתבנו הכל ב-WebGL — קיבלנו 60 פריימים לשנייה יציבים. הלקוח חסך בחומרה והאיץ את טעינת הדשבורד פי 12. הלקוח חסך מעל $12,000 בשנה על שדרוגי חומרה.
אנחנו מתמחים בוויזואליזציות WebGL מזה למעלה מ-10 שנים, ומבטיחים 60 פריימים לשנייה יציבים במכשירי יעד. המהנדסים המוסמכים שלנו מבטיחים איכות מעולה. במהלך הזמן הזה, יישמנו עשרות פרויקטים: ממפות עם מיליוני אובייקטים ועד אפקטי חלקיקים מונפשים. הניסיון שלנו מבטיח שהוויזואליזציה תפעל בצורה חלקה על כל מכשיר — משולחני ועד מובייל בינוני.
איך WebGL משיג 60 פריימים לשנייה על נתונים גדולים
WebGL משתמש ב-GPU וממזער את מספר קריאות הציור. בניגוד ל-Canvas 2D, שמצייר כל נקודה בנפרד, WebGL שולח את כל מערך הנתונים בקריאה אחת. זה מפחית את עומס ה-CPU ומאפשר להשיג 60 פריימים לשנייה אפילו עם מיליון נקודות. גורמים מרכזיים: שימוש במאגרי float, הימנעות מ-uniform מיותרים, ותצורת blending נכונה. לפי WebGL API, כל קריאת ציור יכולה לעבד עד 2^32 קודקודים, מה שמספק מרווח ביצועים עצום. לוויזואליזציית WebGL יעילה, שלבו את deck.gl עם shaders מותאמים אישית לביצועים אופטימליים.
מתי יש צורך ב-WebGL
- תרשים פיזור עם 500,000+ נקודות (נתונים פיננסיים, גיאו-מרחביים)
- מערכות חלקיקים: רקעים אינטראקטיביים, ויזואליזציות של תהליכים פיזיקליים
- מפות חום בזמן אמת (נתוני מסחר במניות, מפות חום של קליקים)
- אנימציות פרוצדורליות (Perlin noise, משטחים מתמטיים)
- עיבוד תמונה דרך shaders (פילטרים, אפקטים)
לתרשימים סטנדרטיים (100–10,000 נקודות), D3.js או Recharts מספיקים.
השוואה בין WebGL, Canvas 2D ו-SVG לנתונים גדולים
| פרמטר | WebGL | Canvas 2D | SVG |
|---|---|---|---|
| מקסימום נקודות (60 פריימים לשנייה) | 1,000,000+ | 10,000 | 1,000 |
| סוג עיבוד | GPU, קריאת ציור | CPU, מיידי | DOM, נשמר |
| ביצועי שיא | 60 פריימים לשנייה | ~30 פריימים לשנייה ב-50 אלף | ~15 פריימים לשנייה ב-10 אלף |
| מורכבות יישום | גבוהה | בינונית | נמוכה |
| תמיכה בדפדפנים ישנים | גיבוי WebGL1 | טובה | מצוינת |
WebGL מהיר פי 10 מ-Canvas 2D ב-500 אלף נקודות — זה ההבדל בין 60 פריימים לשנייה ל-3 פריימים לשנייה. הבחירה תלויה בנפח הנתונים ובדרישות הוויזואליזציה.
השוואה בין ספריות WebGL פופולריות
| ספרייה | מקרי שימוש טיפוסיים | מורכבות כתיבת shader | ביצועים |
|---|---|---|---|
| deck.gl | נתונים גיאוגרפיים, פיזור, מפת חום | נמוכה (שכבות מוכנות) | גבוהים ל-2D |
| regl | אפקטים מותאמים אישית, חלקיקים | בינונית | גבוהים |
| Three.js | סצנות תלת-ממד, אנימציות | גבוהה | גבוהים ל-3D |
| WebGL2 גולמי | גמישות מקסימלית | גבוהה | מקסימליים |
בחירה בין deck.gl ל-shaders מותאמים אישית
לנתונים גיאו-מרחביים (מפות, תרשימי פיזור, מפות חום) אנחנו משתמשים ב-deck.gl — ספרייה מ-Uber המותאמת למערכי נתונים גדולים. היא מספקת רכיבי Layer מוכנים (ScatterplotLayer, HeatmapLayer, ColumnLayer) ותומכת ב-WebGL2. לוויזואליזציות מותאמות אישית (מערכות חלקיקים, גרפיקה פרוצדורלית) אנחנו כותבים shaders ישירות או דרך העטיפה של regl.
deck.gl: ויזואליזציה של נתונים גיאו-מרחביים
npm install deck.gl @deck.gl/layers @deck.gl/react react-map-gl maplibre-gl import DeckGL from '@deck.gl/react'
import { ScatterplotLayer, HeatmapLayer, ColumnLayer } from '@deck.gl/layers'
import Map from 'react-map-gl/maplibre'
interface DataPoint {
coordinates: [number, number]
value: number
category: string
}
function GeoVisualization({ data }: { data: DataPoint[] }) {
const [viewState, setViewState] = useState({
longitude: 37.6,
latitude: 55.75,
zoom: 10,
pitch: 45,
bearing: 0,
})
const layers = [
new ScatterplotLayer({
id: 'scatter',
data,
getPosition: (d) => d.coordinates,
getRadius: (d) => Math.sqrt(d.value) * 10,
getFillColor: (d) => {
const t = d.value / 1000
return [255 * t, 100, 255 * (1 - t), 200]
},
pickable: true,
radiusMinPixels: 2,
radiusMaxPixels: 50,
}),
new HeatmapLayer({
id: 'heatmap',
data,
getPosition: (d) => d.coordinates,
getWeight: (d) => d.value,
radiusPixels: 40,
intensity: 1,
threshold: 0.1,
colorRange: [
[0, 0, 255, 0],
[0, 255, 255, 128],
[0, 255, 0, 200],
[255, 255, 0, 220],
[255, 0, 0, 255],
],
}),
]
return (
<DeckGL
viewState={viewState}
onViewStateChange={({ viewState }) => setViewState(viewState as any)}
layers={layers}
getTooltip={({ object }: { object: DataPoint }) =>
object && {
html: `<b>Значение:</b> ${object.value}`,
style: { background: '#fff' }
}
}
style={{ position: 'relative', height: '600px' }}
controller={true}
>
<Map mapStyle="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" />
</DeckGL>
)
} WebGL shaders ישירות: GLSL
function WebGLCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const gl = canvas.getContext('webgl2')!
const vertexShaderSrc = `#version 300 es
in vec2 a_position;
in float a_value;
out float v_value;
uniform vec2 u_resolution;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
gl_PointSize = max(2.0, sqrt(a_value) * 3.0);
v_value = a_value;
}
`
const fragmentShaderSrc = `#version 300 es
precision highp float;
in float v_value;
out vec4 outColor;
vec3 viridis(float t) {
const vec3 c0 = vec3(0.267, 0.004, 0.329);
const vec3 c1 = vec3(0.127, 0.566, 0.551);
const vec3 c2 = vec3(0.993, 0.906, 0.144);
return mix(mix(c0, c1, t), mix(c1, c2, t), t);
}
void main() {
vec2 coord = gl_PointCoord - 0.5;
if (length(coord) > 0.5) discard;
outColor = vec4(viridis(v_value), 0.8);
}
`
function createShader(type: number, source: string): WebGLShader {
const shader = gl.createShader(type)!
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error(gl.getShaderInfoLog(shader) ?? 'Shader error')
}
return shader
}
const program = gl.createProgram()!
gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexShaderSrc))
gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentShaderSrc))
gl.linkProgram(program)
const N = 100_000
const positions = new Float32Array(N * 2)
const values = new Float32Array(N)
for (let i = 0; i < N; i++) {
positions[i * 2] = Math.random() * canvas.width
positions[i * 2 + 1] = Math.random() * canvas.height
values[i] = Math.random()
}
const posBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer)
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
const aPosition = gl.getAttribLocation(program, 'a_position')
gl.enableVertexAttribArray(aPosition)
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0)
const valBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, valBuffer)
gl.bufferData(gl.ARRAY_BUFFER, values, gl.STATIC_DRAW)
const aValue = gl.getAttribLocation(program, 'a_value')
gl.enableVertexAttribArray(aValue)
gl.vertexAttribPointer(aValue, 1, gl.FLOAT, false, 0, 0)
gl.useProgram(program)
gl.uniform2f(gl.getUniformLocation(program, 'u_resolution'), canvas.width, canvas.height)
gl.clearColor(0.05, 0.05, 0.1, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.drawArrays(gl.POINTS, 0, N)
}, [])
return <canvas ref={canvasRef} width={800} height={600} />
} Regl: עטיפה נוחה מעל WebGL
npm install regl npm install -D @types/regl import createREGL from 'regl'
const regl = createREGL(canvasRef.current!)
const drawParticles = regl({
vert: `
precision mediump float;
attribute vec2 position;
attribute float age;
uniform float time;
void main() {
vec2 pos = position + vec2(cos(time + age), sin(time * 0.7 + age)) * 0.05;
gl_Position = vec4(pos, 0, 1);
gl_PointSize = 3.0;
}
`,
frag: `
precision mediump float;
void main() {
gl_FragColor = vec4(0.4, 0.8, 1.0, 0.7);
}
`,
attributes: {
position: particlePositions,
age: particleAges,
},
uniforms: {
time: regl.context('time'),
},
count: PARTICLE_COUNT,
primitive: 'points',
})
regl.frame(({ time }) => {
regl.clear({ color: [0, 0, 0.1, 1], depth: 1 })
drawParticles()
}) מה כלול
- ניתוח נתונים ובחירת טכנולוגיה מתאימה (deck.gl, WebGL גולמי, regl)
- יישום ויזואליזציה תוך התחשבות בביצועים (טעינה אסינכרונית, LOD)
- אופטימיזציה ל-60 פריימים לשנייה במכשירי מובייל בינוניים
- כתיבת shaders מותאמים אישית במידת הצורך
- תיעוד לשילוב ושימוש
- הכשרת צוות הלקוח לעבודה עם הקוד
- תמיכה חינמית למשך 14 יום לאחר המסירה
תהליך עבודה
- אנליטיקה — אנו חוקרים את נפח ומבנה הנתונים, קובעים את סוג הוויזואליזציה
- עיצוב — בוחרים ספרייה או WebGL גולמי, מתכננים shaders
- יישום — כותבים קוד, משתלבים עם CMS/React/Next.js
- בדיקות — בודקים ביצועים על שולחני, טאבלט, מובייל
- פריסה — מפרסמים, מגדירים CDN לטקסטורות ונתונים
לוחות זמנים משוערים
- ויזואליזציה בסיסית עם deck.gl (תרשים פיזור, מפת חום) — 3–4 ימים
- Shaders מותאמים אישית ומערכות חלקיקים — 7–10 ימים
- ויזואליזציה מורכבת עם מספר שכבות ואנימציה — עד 3 שבועות
התמחור מחושב באופן אישי, בהתבסס על מורכבות ה-shader, נפח הנתונים והצורך בתמיכה בדפדפנים ישנים.
טעויות טיפוסיות ביישום WebGL
- שימוש ב-WebGL לתרשימים פשוטים (עד 10 אלף נקודות) — מוגזם, Canvas 2D יספיק.
- אי התחשבות בביצועים במכשירי מובייל — לטלפונים בינוניים יש לרוב GPUs חלשים.
- שכחת גיבוי ל-WebGL1 עבור Safari <15 או אנדרואיד ישן.
הזמינו ויזואליזציית WebGL — קבלו ייעוץ תוך יום. המהנדסים שלנו עם ניסיון של 10+ שנים יעזרו לכם לבחור את הפתרון האופטימלי וליישם את הפרויקט במפתח מלא. קבלו ייעוץ על הפרויקט שלכם — צרו קשר כדי לדון.







