← Все статьи

API и хелперы

Собери наблюдатель цены и наличия на MutationObserver

Ты хочешь этот товар, но только по нужной цене — или хочешь узнать в тот же миг, как распроданный продукт снова в наличии. Обновлять вкладку весь день — это не жизнь. Эта статья собирает наблюдатель цены и наличия JS-правилом JustZix: MutationObserver следит за нужным элементом, сравнивает значение и уведомляет тебя, когда твоё условие выполнено.

Как работает наблюдатель

План состоит из четырёх частей, и каждая соответствует разделу ниже:

  1. Выбери стабильный селектор для элемента цены или наличия.
  2. Прикрепи MutationObserver, чтобы реагировать на изменения без опроса.
  3. Разбери текст в сравнимое число или булево значение.
  4. Уведоми себя — баннером на странице или системным уведомлением — и запомни последнее значение.

Всё работает на стороне клиента во вкладке. Держи страницу товара открытой (закреплённая вкладка идеальна) — и правило само следит.

Выбор стабильного селектора

Наблюдатель надёжен ровно настолько, насколько надёжен его селектор. Открой DevTools, панель Elements, и найди элемент, содержащий цену. Предпочитай в таком порядке:

Избегай хэшированных классов (.css-1a2b3c) и длинных цепочек потомков — они ломаются на следующем деплое. Многие магазины ещё встраивают структурированные данные в блок <script type="application/ld+json">; если видимый DOM беспорядочный, разбор этого JSON часто оказывается самым стабильным маршрутом.

Настройка MutationObserver

MutationObserver вызывает колбэк всякий раз, когда наблюдаемое поддерево меняется, — без опроса, без впустую потраченного CPU. Следи за узлом цены на изменения текста и потомков:

const SELECTOR = '[itemprop="price"]';   // your stable selector
const priceEl = document.querySelector(SELECTOR);

if (!priceEl) {
  console.warn('[watcher] price element not found - check the selector');
} else {
  const observer = new MutationObserver(() => checkPrice(priceEl));
  observer.observe(priceEl, {
    childList: true,
    subtree: true,
    characterData: true
  });

  // Also check once on load - the price may already be a deal
  checkPrice(priceEl);
}

Магазины-SPA иногда заменяют весь узел цены при обновлении. Если твой наблюдатель замолк, наблюдай за стабильным родительским контейнером с subtree: true и перезапрашивай цену внутри колбэка.

Разбор цены в число

Текст цены беспорядочен: символы валют, разделители тысяч, десятичные запятые. Приведи его к чистому числу перед сравнением:

// Turn "$1,299.00" or "1 299,00 zl" into 1299
function parsePrice(text) {
  if (!text) return NaN;
  // Keep digits, dot and comma; drop everything else
  let s = text.replace(/[^0-9.,]/g, '');
  // If comma is the decimal separator, normalise it
  if (/,\d{2}$/.test(s)) s = s.replace(/\./g, '').replace(',', '.');
  else s = s.replace(/,/g, '');
  return parseFloat(s);
}

Сравнение с порогом

Теперь логика решения. Сравни разобранную цену с твоей целью и уведомляй только когда она действительно пересекает порог — и только один раз на падение, чтобы тебя не спамило на каждой мелкой мутации DOM:

const TARGET = 999;            // alert when price drops to/below this
const STORAGE_KEY = 'jz-watch-' + location.pathname;

function checkPrice(el) {
  const price = parsePrice(el.textContent);
  if (Number.isNaN(price)) return;

  const last = parseFloat(localStorage.getItem(STORAGE_KEY)) || Infinity;
  localStorage.setItem(STORAGE_KEY, String(price));

  console.log('[watcher] price now', price, '- last', last);

  // Fire only when we newly cross the threshold
  if (price <= TARGET && last > TARGET) {
    notify('Price drop! Now ' + price + ' (target ' + TARGET + ')');
  }
}

Следим за возвратом в наличие

Тот же паттерн, другой сигнал. Вместо числа ты следишь за булевым значением — включена ли кнопка покупки, пропала ли надпись «нет в наличии»:

function checkStock() {
  const soldOut = document.querySelector('[class*="out-of-stock" i], .sold-out');
  const buyBtn = document.querySelector('button[name="add-to-cart"], .add-to-cart');
  const inStock = !soldOut && buyBtn && !buyBtn.disabled;

  const wasInStock = localStorage.getItem('jz-stock') === 'yes';
  localStorage.setItem('jz-stock', inStock ? 'yes' : 'no');

  if (inStock && !wasInStock) {
    notify('Back in stock! Grab it now.');
  }
}

Уведомляем себя

Уведомление бесполезно, если ты его не видишь. Используй два канала: непропускаемый баннер на странице плюс системное уведомление на случай, когда вкладка в фоне.

function notify(message) {
  // 1. On-page banner - always visible in the tab
  let bar = document.getElementById('jz-watch-bar');
  if (!bar) {
    bar = document.createElement('div');
    bar.id = 'jz-watch-bar';
    bar.style.cssText =
      'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
      'background:#16a34a;color:#fff;font:600 15px/1.4 sans-serif;' +
      'padding:12px 16px;text-align:center;';
    document.body.appendChild(bar);
  }
  bar.textContent = 'JZ watcher: ' + message;

  // 2. System notification - works when the tab is hidden
  if (Notification.permission === 'granted') {
    new Notification('JustZix watcher', { body: message });
  } else if (Notification.permission !== 'denied') {
    Notification.requestPermission();
  }
}

z-index в 2147483647 — это максимальное 32-битное целое; оно гарантирует, что баннер сидит выше всего, что рендерит сайт. Системным уведомлениям нужно разрешение, которое браузер выдаёт только по жесту пользователя или для ранее одобренного origin, так что первый запуск может просто запросить его; баннер прикрывает тебя в это время.

Сохраняем последнее увиденное значение

Ты видел использование localStorage выше, и оно делает реальную работу. Оно переживает перезагрузки, поэтому наблюдатель знает, действительно ли изменение новое. Оно также устраняет дубли уведомлений — ты сравниваешь с сохранённым значением и уведомляешь только на настоящем пересечении. Делай ключ для каждой страницы (location.pathname), чтобы несколько наблюдателей товаров сосуществовали, не перезаписывая друг друга.

Подключаем это в JustZix

  1. Создай правило с URL-паттерном конкретного товара, например https://shop.example.com/product/12345*.
  2. Вставь объединённый скрипт во вкладку JS — задай SELECTOR и TARGET для этого товара.
  3. Закрепи вкладку и оставь открытой; наблюдатель реагирует, когда магазин обновляет DOM.
  4. Хочешь периодическую перепроверку даже без изменений DOM? Добавь setInterval, который перезагружает страницу каждые несколько минут.

Смотри также

Хватит обновлять эту вкладку. Установи JustZix, помести скрипт наблюдателя в правило для конкретного товара — и пусть браузер сам скажет тебе в тот миг, когда цена правильная.

Оцени эту статью

Оценок пока нет — оцени первым.

Попробуй сам

Установи JustZix и вставь любой сниппет из этой статьи. Две минуты от нуля до работающего правила на всех твоих устройствах.

Получить JustZix

Возможности · Как это работает · Примеры · Применение