'use strict'; /* ═══════════════════════════════════════════════════════════════ Калькулятор скорости передачи данных - интерактив для 10 класса ═══════════════════════════════════════════════════════════════ */ (function () { /* ── Таблица перевода размера в байты ───────────────────────── Используем двоичные приставки, как принято для файлов. */ var SIZE_TO_BYTES = { 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024 }; /* ── Таблица перевода скорости в биты в секунду ───────────── Для скорости используем десятичные приставки (стандарт связи). */ var SPEED_TO_BPS = { 'Kbps': 1000, 'Mbps': 1000 * 1000, 'Gbps': 1000 * 1000 * 1000 }; /* ── Таблица времени в секунды ─────────────────────────── */ var TIME_TO_SEC = { 'sec': 1, 'min': 60, 'hour': 3600 }; /* ── Быстрый доступ к элементу ─────────────────────────── */ function q(id) { return document.getElementById(id); } /* ── Форматирование числа с разделителем ───────────────── */ function fmt(n, digits) { if (!isFinite(n)) return '∞'; digits = (digits === undefined) ? 2 : digits; return n.toLocaleString('ru-RU', { minimumFractionDigits: 0, maximumFractionDigits: digits }); } /* ── Форматирование времени в человеческий вид ─────────── */ function fmtTime(sec) { if (!isFinite(sec) || sec < 0) return '-'; if (sec < 1) { return fmt(sec * 1000, 0) + ' мс'; } if (sec < 60) { return fmt(sec, 2) + ' ' + plural(sec, ['секунда', 'секунды', 'секунд']); } if (sec < 3600) { var m = Math.floor(sec / 60); var s = Math.round(sec % 60); return m + ' мин ' + s + ' с'; } if (sec < 86400) { var h = Math.floor(sec / 3600); var m2 = Math.round((sec % 3600) / 60); return h + ' ч ' + m2 + ' мин'; } var d = Math.floor(sec / 86400); var h2 = Math.round((sec % 86400) / 3600); return d + ' ' + plural(d, ['день', 'дня', 'дней']) + ' ' + h2 + ' ч'; } /* ── Форматирование скорости ───────────────────────────── */ function fmtSpeed(bps) { if (!isFinite(bps) || bps <= 0) return '-'; if (bps < 1000) return fmt(bps, 2) + ' бит/с'; if (bps < 1000 * 1000) return fmt(bps / 1000, 2) + ' Кбит/с'; if (bps < 1000 * 1000 * 1000) return fmt(bps / 1e6, 2) + ' Мбит/с'; return fmt(bps / 1e9, 2) + ' Гбит/с'; } /* ── Форматирование размера ────────────────────────────── */ function fmtSize(bytes) { if (bytes < 1024) return fmt(bytes, 0) + ' Б'; if (bytes < 1024 * 1024) return fmt(bytes / 1024, 2) + ' КБ'; if (bytes < 1024 * 1024 * 1024) return fmt(bytes / (1024 * 1024), 2) + ' МБ'; return fmt(bytes / (1024 * 1024 * 1024), 2) + ' ГБ'; } /* ── Русские склонения ─────────────────────────────────── */ function plural(n, forms) { var m10 = n % 10; var m100 = n % 100; if (m10 === 1 && m100 !== 11) return forms[0]; if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) return forms[1]; return forms[2]; } /* ── Экранирование HTML ───────────────────────────────── */ function esc(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } /* ═══ РЕЖИМ 1: ВРЕМЯ ЗАГРУЗКИ ═══════════════════════════ */ function calcTime() { var sizeVal = parseFloat(q('scTimeSize').value); var sizeUnit = q('scTimeSizeUnit').value; var speedVal = parseFloat(q('scTimeSpeed').value); var speedUnit = q('scTimeSpeedUnit').value; var resultEl = q('scTimeResult'); var resultSubEl = q('scTimeResultSub'); var stepsEl = q('scTimeSteps'); var compareEl = q('scTimeCompare'); if (!(sizeVal > 0) || !(speedVal > 0)) { resultEl.textContent = '-'; resultSubEl.textContent = 'введите положительные числа'; stepsEl.innerHTML = ''; compareEl.innerHTML = ''; return; } /* 1. Переводим объём в байты */ var sizeBytes = sizeVal * SIZE_TO_BYTES[sizeUnit]; /* 2. Переводим объём в биты (умножаем на 8) */ var sizeBits = sizeBytes * 8; /* 3. Переводим скорость в биты в секунду */ var speedBps = speedVal * SPEED_TO_BPS[speedUnit]; /* 4. Время в секундах */ var timeSec = sizeBits / speedBps; /* Показываем результат */ resultEl.textContent = fmtTime(timeSec); resultSubEl.textContent = 'файл ' + fmtSize(sizeBytes) + ' при скорости ' + fmtSpeed(speedBps) + ' (' + fmt(timeSec, 3) + ' секунд)'; /* Разбор по шагам */ var steps = [ 'Переводим размер файла в байты: ' + fmt(sizeVal, 2) + ' ' + sizeUnit + ' = ' + fmt(sizeBytes, 0) + ' байт', 'Переводим байты в биты (умножаем на 8): ' + fmt(sizeBytes, 0) + ' × 8 = ' + fmt(sizeBits, 0) + ' бит', 'Переводим скорость в биты в секунду: ' + fmt(speedVal, 2) + ' ' + speedUnit + ' = ' + fmt(speedBps, 0) + ' бит/с', 'Считаем время по формуле t = V / v:
' + 't = ' + fmt(sizeBits, 0) + ' / ' + fmt(speedBps, 0) + ' = ' + fmt(timeSec, 3) + ' секунд', 'Округляем до удобного вида: ' + fmtTime(timeSec) + '' ]; renderSteps(stepsEl, steps); /* Сравнение с технологиями */ renderTimeCompare(compareEl, sizeBits); } function renderTimeCompare(container, sizeBits) { var techs = [ { name: 'Dial-up', bps: 56 * 1000 }, { name: '3G', bps: 2 * 1000 * 1000 }, { name: '4G LTE', bps: 30 * 1000 * 1000 }, { name: 'Wi-Fi 100', bps: 100 * 1000 * 1000 }, { name: '5G', bps: 500 * 1000 * 1000 }, { name: 'Оптоволокно', bps: 1000 * 1000 * 1000 } ]; /* Находим максимальное время для нормирования полосок */ var maxTime = 0; techs.forEach(function (t) { var sec = sizeBits / t.bps; if (sec > maxTime) maxTime = sec; }); var html = techs.map(function (t) { var sec = sizeBits / t.bps; var pct = maxTime > 0 ? Math.max(2, sec / maxTime * 100) : 0; return '
' + '
' + esc(t.name) + '
' + '
' + '
' + '
' + '
' + fmtTime(sec) + '
' + '
'; }).join(''); container.innerHTML = html; } /* ═══ РЕЖИМ 2: НУЖНАЯ СКОРОСТЬ ══════════════════════════ */ function calcSpeed() { var sizeVal = parseFloat(q('scSpeedSize').value); var sizeUnit = q('scSpeedSizeUnit').value; var timeVal = parseFloat(q('scSpeedTime').value); var timeUnit = q('scSpeedTimeUnit').value; var resultEl = q('scSpeedResult'); var resultSubEl = q('scSpeedResultSub'); var stepsEl = q('scSpeedSteps'); if (!(sizeVal > 0) || !(timeVal > 0)) { resultEl.textContent = '-'; resultSubEl.textContent = 'введите положительные числа'; stepsEl.innerHTML = ''; return; } /* 1. Размер в байтах → в биты */ var sizeBytes = sizeVal * SIZE_TO_BYTES[sizeUnit]; var sizeBits = sizeBytes * 8; /* 2. Время в секундах */ var timeSec = timeVal * TIME_TO_SEC[timeUnit]; /* 3. Нужная скорость в бит/с */ var speedBps = sizeBits / timeSec; /* 4. В Мбит/с для удобства */ var speedMbps = speedBps / 1e6; resultEl.textContent = fmt(speedMbps, 2) + ' Мбит/с'; resultSubEl.textContent = 'это ' + fmtSpeed(speedBps) + ' или ' + fmt(speedBps / 8 / 1024 / 1024, 2) + ' МБ/с'; var steps = [ 'Размер файла: ' + fmt(sizeVal, 2) + ' ' + sizeUnit + ' = ' + fmt(sizeBytes, 0) + ' байт = ' + fmt(sizeBits, 0) + ' бит', 'Желаемое время: ' + fmt(timeVal, 2) + ' ' + timeUnit + ' = ' + fmt(timeSec, 0) + ' секунд', 'Считаем нужную скорость по формуле v = V / t:
' + 'v = ' + fmt(sizeBits, 0) + ' / ' + fmt(timeSec, 0) + ' = ' + fmt(speedBps, 0) + ' бит/с', 'Переводим в удобные единицы: ' + fmt(speedBps, 0) + ' бит/с = ' + fmt(speedMbps, 2) + ' Мбит/с', 'Итог: нужен тариф не ниже ' + fmt(speedMbps, 2) + ' Мбит/с (при условии идеального канала)' ]; renderSteps(stepsEl, steps); } /* ═══ ВСПОМОГАТЕЛЬНЫЕ ════════════════════════════════════ */ function renderSteps(container, steps) { container.innerHTML = steps.map(function (text, i) { return '
' + '
' + (i + 1) + '
' + '
' + text + '
' + '
'; }).join(''); } /* ═══ ПРЕСЕТЫ ════════════════════════════════════════════ */ var PRESETS_TIME = { photo: { size: 6, sizeUnit: 'MB', speed: 24, speedUnit: 'Mbps' }, song: { size: 8, sizeUnit: 'MB', speed: 4, speedUnit: 'Mbps' }, movie: { size: 2, sizeUnit: 'GB', speed: 100, speedUnit: 'Mbps' }, game: { size: 50, sizeUnit: 'GB', speed: 500, speedUnit: 'Mbps' } }; var PRESETS_SPEED = { video: { size: 1.5, sizeUnit: 'GB', time: 4, timeUnit: 'min' }, update: { size: 12, sizeUnit: 'GB', time: 20, timeUnit: 'min' }, backup: { size: 100, sizeUnit: 'GB', time: 1, timeUnit: 'hour' } }; function applyTimePreset(key) { var p = PRESETS_TIME[key]; if (!p) return; q('scTimeSize').value = p.size; q('scTimeSizeUnit').value = p.sizeUnit; q('scTimeSpeed').value = p.speed; q('scTimeSpeedUnit').value = p.speedUnit; calcTime(); } function applySpeedPreset(key) { var p = PRESETS_SPEED[key]; if (!p) return; q('scSpeedSize').value = p.size; q('scSpeedSizeUnit').value = p.sizeUnit; q('scSpeedTime').value = p.time; q('scSpeedTimeUnit').value = p.timeUnit; calcSpeed(); } /* ═══ ПЕРЕКЛЮЧЕНИЕ ТАБОВ ═════════════════════════════════ */ function switchTab(name) { document.querySelectorAll('.sc-tab').forEach(function (t) { var on = t.dataset.tab === name; t.classList.toggle('active', on); t.setAttribute('aria-selected', String(on)); }); document.querySelectorAll('.sc-panel').forEach(function (p) { p.classList.toggle('active', p.id === 'scPanel' + capitalize(name)); }); } function capitalize(s) { return s.charAt(0).toUpperCase() + s.slice(1); } /* ═══ ОБРАБОТЧИКИ ════════════════════════════════════════ */ function bindEvents() { /* Делегированный клик - надёжно ловит любые кнопки */ document.addEventListener('click', function (e) { if (!e.target.closest) return; var tab = e.target.closest('.sc-tab'); if (tab) { switchTab(tab.dataset.tab); return; } var presetTime = e.target.closest('[data-preset]'); if (presetTime) { applyTimePreset(presetTime.dataset.preset); return; } var presetSpeed = e.target.closest('[data-preset2]'); if (presetSpeed) { applySpeedPreset(presetSpeed.dataset.preset2); return; } }); /* Ввод в первой панели */ ['scTimeSize', 'scTimeSizeUnit', 'scTimeSpeed', 'scTimeSpeedUnit'].forEach(function (id) { var el = q(id); if (el) { el.addEventListener('input', calcTime); el.addEventListener('change', calcTime); } }); /* Ввод во второй панели */ ['scSpeedSize', 'scSpeedSizeUnit', 'scSpeedTime', 'scSpeedTimeUnit'].forEach(function (id) { var el = q(id); if (el) { el.addEventListener('input', calcSpeed); el.addEventListener('change', calcSpeed); } }); } /* ═══ ИНИЦИАЛИЗАЦИЯ ══════════════════════════════════════ */ function init() { if (!q('scPanelTime')) return; bindEvents(); /* Первый расчёт при загрузке - чтобы сразу было что показать */ calcTime(); calcSpeed(); if (window.console && console.debug) { console.debug('[speed-calculator] скрипт загружен'); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();