Реалізація Progress Bar прочитання статті на сайті

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

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Progress Bar прочитання статті на сайті
Проста
від 4 годин до 2 робочих днів
Часті питання

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

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

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

  • 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

Реалізація Progress Bar прочитання статті на сайті

Progress bar прочитання — тонка смужка в верхній частині сторінки, яка показує, скільки статті користувач прочитав. Застосовується на блогах та медіа-сайтах. Деталь: рахувати прогрес потрібно не від висоти всієї сторінки, а лише від висоти статті.

Базова реалізація

<div class="reading-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100"
  aria-valuenow="0" aria-label="Прогрес прочитання">
  <div class="reading-progress__bar"></div>
</div>
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 9999;
  background: transparent;
}

.reading-progress__bar {
  height: 100%;
  width: 0%;
  background: linear-gradient(to right, #6366f1, #8b5cf6);
  transition: width 0.1s linear;
  transform-origin: left;
}

/* Під sticky header */
.site-header ~ .reading-progress,
.site-header + * .reading-progress {
  top: var(--header-height, 64px);
}
function initReadingProgress(articleSelector: string = 'article, main, .post-content') {
  const bar = document.querySelector('.reading-progress__bar') as HTMLElement
  const progressEl = document.querySelector('.reading-progress') as HTMLElement
  const article = document.querySelector(articleSelector) as HTMLElement

  if (!bar || !article) return

  function update() {
    const articleRect = article.getBoundingClientRect()
    const articleTop = articleRect.top + window.scrollY
    const articleBottom = articleTop + article.offsetHeight

    // Прогрес відносно статті, а не всієї сторінки
    const viewportBottom = window.scrollY + window.innerHeight
    const readAmount = viewportBottom - articleTop
    const totalToRead = article.offsetHeight

    const progress = Math.min(100, Math.max(0, (readAmount / totalToRead) * 100))

    bar.style.width = `${progress}%`
    progressEl.setAttribute('aria-valuenow', String(Math.round(progress)))
  }

  let ticking = false
  window.addEventListener('scroll', () => {
    if (ticking) return
    ticking = true
    requestAnimationFrame(() => {
      update()
      ticking = false
    })
  }, { passive: true })

  update()
}

React-хук та компонент

import { useEffect, useState, useRef } from 'react'

function useReadingProgress(contentRef: React.RefObject<HTMLElement>) {
  const [progress, setProgress] = useState(0)

  useEffect(() => {
    let ticking = false

    function calculate() {
      const el = contentRef.current
      if (!el) return

      const { top, height } = el.getBoundingClientRect()
      const absoluteTop = top + window.scrollY
      const readAmount = window.scrollY + window.innerHeight - absoluteTop
      const pct = Math.min(100, Math.max(0, (readAmount / height) * 100))
      setProgress(pct)
    }

    const handler = () => {
      if (ticking) return
      ticking = true
      requestAnimationFrame(() => {
        calculate()
        ticking = false
      })
    }

    window.addEventListener('scroll', handler, { passive: true })
    window.addEventListener('resize', handler, { passive: true })
    calculate()

    return () => {
      window.removeEventListener('scroll', handler)
      window.removeEventListener('resize', handler)
    }
  }, [contentRef])

  return progress
}

export function ReadingProgressBar({ contentRef }: { contentRef: React.RefObject<HTMLElement> }) {
  const progress = useReadingProgress(contentRef)

  return (
    <div
      className="reading-progress"
      role="progressbar"
      aria-valuemin={0}
      aria-valuemax={100}
      aria-valuenow={Math.round(progress)}
      aria-label="Прогрес прочитання статті"
    >
      <div
        className="reading-progress__bar"
        style={{ width: `${progress}%` }}
      />
    </div>
  )
}

// Використання:
function ArticlePage({ post }: { post: Post }) {
  const contentRef = useRef<HTMLDivElement>(null)

  return (
    <>
      <ReadingProgressBar contentRef={contentRef} />
      <article>
        <h1>{post.title}</h1>
        <div ref={contentRef} className="post-content">
          {post.content}
        </div>
      </article>
    </>
  )
}

Варіації дизайну

/* Варіант 1: Смужка справа (вертикальна) */
.reading-progress--vertical {
  position: fixed;
  top: 0;
  right: 0;
  width: 3px;
  height: 100%;
  bottom: auto;
  left: auto;
}

.reading-progress--vertical .reading-progress__bar {
  width: 100%;
  height: 0%;
  transition: height 0.1s linear;
}

/* Варіант 2: У самому хедері */
.site-header::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  background: #6366f1;
  width: var(--reading-progress, 0%);
  transition: width 0.1s linear;
}
// CSS custom property замість прямого управління елементом
window.addEventListener('scroll', () => {
  const progress = calculateProgress()
  document.documentElement.style.setProperty('--reading-progress', `${progress}%`)
}, { passive: true })

Аналітика: час читання та scroll depth

const MILESTONES = [25, 50, 75, 90, 100]
const reported = new Set<number>()

window.addEventListener('scroll', () => {
  const progress = Math.round(calculateProgress())

  MILESTONES.forEach(milestone => {
    if (progress >= milestone && !reported.has(milestone)) {
      reported.add(milestone)

      // Google Analytics 4
      gtag('event', 'scroll_depth', {
        event_category: 'reading',
        value: milestone,
        page_title: document.title,
      })

      // Yandex.Metrika
      ym(COUNTER_ID, 'reachGoal', `scroll_${milestone}`, { percent: milestone })
    }
  })
}, { passive: true })

Терміни

Смужка прогресу — 2–3 години разом із версткою та інтеграцією в шаблон. З аналітикою та варіативним дизайном — половина дня.