Реалізація WebGL-візуалізації на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація WebGL-візуалізації на сайті
Складна
~2-4 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація WebGL-візуалізації на сайті

WebGL — прямий доступ до GPU з браузера. Це не про 3D-моделі — це про візуалізацію даних з сотнями тисяч точок, процедурну графіку, particle-системи, кастомні шейдери. Canvas 2D та SVG не впоруються з великими даними: тисячі DOM-вузлів вбивають продуктивність. WebGL рендерить мільйон точок за один draw call.

Коли потрібен саме WebGL

  • Scatter plot з 500 000+ точок (фінансові дані, геопросторові)
  • Particle systems: інтерактивні фони, візуалізації фізичних процесів
  • Heatmaps в реальному часі (дані біржових торгів, теплові карти кліків)
  • Процедурні анімації (шум Перліна, математичні поверхні)
  • Обробка зображень через шейдери (фільтри, ефекти)

Для стандартних графіків (100–10 000 точок) — D3.js або Recharts достатньо.

deck.gl: візуалізація геопросторових даних

deck.gl від Uber — WebGL-бібліотека для роботи з картами і великими датасетами.

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 шейдери безпосередньо: 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)

    // Генеруємо 100 000 точок
    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()
    }

    // Завантажуємо дані в GPU
    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)

    // Рендеримо 100 000 точок за один draw call
    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!)

// Particle system
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, particle-системи і кастомні шейдери — raw WebGL або regl, scatter plots на картах — MapLibre + deck.gl. Оптимізуємо під 60 fps, тестуємо на середньому діапазоні мобільних пристроїв.

Строк: базова WebGL-візуалізація з готовою бібліотекою — 3–4 дні. Кастомні шейдери та складні particle-системи — 7–10 днів.