Реализация 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 часа вместе с вёрсткой и интеграцией в шаблон. С аналитикой и вариативным дизайном — полдня.