Kalkulator Single Speed
Kalkulator optymalnego przełożenia Single Speed
Chcesz wiedzieć, jaka tylna zębatka będzie najlepsza dla Twojego roweru Single Speed?
Skorzystaj z naszego kalkulatora i sprawdź, jakie przełożenie sprawdzi się najlepiej w zależności od trasy, którą planujesz pokonać.
Jak to działa?
- Wprowadź dane swojego roweru i wagi kolarza – wystarczy zrobić to raz.
- Dodaj odcinki trasy:
- Dystans w kilometrach,
- Rodzaj terenu (płaski, pagórkowaty, stromy),
- Średnie nachylenie w procentach.
- Możesz dodać dowolną liczbę odcinków – np. 10 km płaskiego, 5 km pod górę, 3 km stromego podjazdu.
- Podaj liczbę zębów przedniej zębatki (np. 42).
- Kalkulator wyliczy optymalną tylną zębatkę oraz poda najbliższy dostępny wolnobieg (np. 16T, 18T, 20T).
Algorytm korzysta z uproszczonego modelu, który uwzględnia:
- średnie nachylenie trasy,
- udział poszczególnych rodzajów terenu w całej trasie,
- wagę roweru i kolarza (do korekty trudności jazdy).
FAQ
Czy kalkulator poda mi jedną idealną wartość?
Nie do końca – poda wartość obliczoną oraz najbliższy dostępny wolnobieg. To sugestia, a nie absolutna odpowiedź.
Co jeśli jeżdżę w różnych warunkach (miasto + góry)?
Możesz dodać różne trasy do tabeli i sprawdzić, jakie przełożenia są sugerowane w każdym scenariuszu.
Czy wynik nadaje się do ścigania?
Kalkulator jest przybliżonym narzędziem – dobór przełożenia do wyścigów zależy też od stylu jazdy, kadencji i preferencji.
Jakie przełożenie jest najczęściej wybierane?
Na szosie popularne są zestawy typu 42/16 lub 46/17. W trudniejszym terenie warto rozważyć większą tylną zębatkę (np. 18–20T).
Spróbuj teraz!
Wprowadź swoje dane i zobacz, jakie przełożenie Single Speed będzie najlepsze na Twoją trasę.
# | Dystans (km) | Teren | Nachylenie (%) |
---|
Wynik pojawi się tutaj...
Kod po stronie przeglądarki
<div id="app">
<!-- Parametry roweru i kolarza -->
<div class="card mb-3">
<div class="card-header">Parametry roweru i kolarza</div>
<div class="card-body">
<div class="row g-2">
<div class="col-sm-2">
<label for="bikeWeight" class="form-label small">Waga roweru (kg)</label>
<input type="number" class="form-control" id="bikeWeight" value="8.5">
</div>
<div class="col-sm-2">
<label for="riderWeight" class="form-label small">Waga kolarza (kg)</label>
<input type="number" class="form-control" id="riderWeight" value="75">
</div>
<div class="col-sm-2">
<label for="wheelSize" class="form-label small">Średnica koła (mm)</label>
<input type="number" class="form-control" id="wheelSize" value="700">
</div>
<div class="col-sm-2">
<label for="tireWidth" class="form-label small">Szerokość opony (mm)</label>
<input type="number" class="form-control" id="tireWidth" value="28">
</div>
<div class="col-sm-2">
<label for="bikeType" class="form-label small">Typ roweru</label>
<select class="form-select" id="bikeType">
<option value="road">Szosa</option>
<option value="gravel">Szuter</option>
<option value="urban">Miasto</option>
</select>
</div>
</div>
</div>
</div>
<!-- Tabela odcinków -->
<div class="card mb-3">
<div class="card-header">Odcinki trasy</div>
<div class="card-body">
<div class="row mb-2">
<div class="col-sm-3">
<input type="number" step="0.1" class="form-control" id="distance" placeholder="Dystans (km)" aria-label="Dystans trasy">
</div>
<div class="col-sm-3">
<select class="form-select" id="terrain" aria-label="Rodzaj terenu">
<option value="flat">Płaski</option>
<option value="hilly">Pagórkowaty</option>
<option value="steep">Stromy</option>
</select>
</div>
<div class="col-sm-3">
<input type="number" step="0.1" class="form-control" id="slope" placeholder="Nachylenie (%)" aria-label="Nachylenie terenu">
</div>
<div class="col-sm-3">
<button class="btn btn-primary w-100" id="addSegmentBtn">Dodaj odcinek</button>
</div>
</div>
<table class="table table-sm table-striped">
<thead class="table-dark">
<tr>
<th>#</th>
<th>Dystans (km)</th>
<th>Teren</th>
<th>Nachylenie (%)</th>
</tr>
</thead>
<tbody id="segmentsBody"></tbody>
</table>
<div class="d-flex gap-2">
<button class="btn btn-secondary" id="loadDefaultSegments">Wczytaj przykładową trasę</button>
<button class="btn btn-outline-danger" id="clearSegments">Wyczyść odcinki</button>
</div>
</div>
</div>
<!-- Obliczenia przełożenia -->
<div class="card">
<div class="card-body">
<div class="row align-items-center">
<div class="col-sm-4">
<label for="frontTeeth" class="form-label small">Zęby przedniej zębatki</label>
<input type="number" class="form-control" id="frontTeeth" value="42">
</div>
<div class="col-sm-4">
<button class="btn btn-success mt-4" id="calcBtn">Oblicz optymalne przełożenie</button>
</div>
</div>
<div class="mt-3">
<h3 class="h4" id="result">Wynik pojawi się tutaj...</h3>
<pre id="details" style="display:none; background:#f8f9fa; padding:10px; border-radius:6px;"></pre>
</div>
</div>
</div>
</div>
<script>
let segmentCounter = 0;
const TERRAIN_LABEL = { flat: 'Płaski', hilly: 'Pagórkowaty', steep: 'Stromy' };
const DEFAULT_SEGMENTS = [
{ distance: 10, terrain: 'flat', slope: 0 },
{ distance: 5, terrain: 'hilly', slope: 4 },
{ distance: 3, terrain: 'steep', slope: 8 },
{ distance: 12, terrain: 'flat', slope: 1 }
];
function addSegment(data) {
const distance = data ? parseFloat(data.distance) : parseFloat(document.getElementById('distance').value);
const terrain = data ? data.terrain : document.getElementById('terrain').value;
const slope = data ? parseFloat(data.slope) : parseFloat(document.getElementById('slope').value);
if (Number.isNaN(distance) || Number.isNaN(slope)) {
alert('Podaj poprawne wartości dystansu i nachylenia.');
return;
}
segmentCounter++;
const tr = document.createElement('tr');
tr.dataset.distance = distance;
tr.dataset.terrain = terrain;
tr.dataset.slope = slope;
tr.innerHTML = `
<td>${segmentCounter}</td>
<td>${distance.toFixed(1)}</td>
<td>${TERRAIN_LABEL[terrain]}</td>
<td>${slope.toFixed(1)}</td>
`;
document.getElementById('segmentsBody').appendChild(tr);
}
function loadDefaultSegments() {
clearSegments();
DEFAULT_SEGMENTS.forEach(seg => addSegment(seg));
}
function clearSegments() {
document.getElementById('segmentsBody').innerHTML = '';
segmentCounter = 0;
}
function calculateGear() {
const frontTeeth = parseInt(document.getElementById('frontTeeth').value, 10);
const rows = Array.from(document.querySelectorAll('#segmentsBody tr'));
if (!frontTeeth || frontTeeth <= 0) {
alert('Wprowadź poprawną liczbę zębów przedniej zębatki.');
return;
}
if (rows.length === 0) {
alert('Dodaj odcinki trasy.');
return;
}
// dane roweru i kolarza
const bikeWeight = parseFloat(document.getElementById('bikeWeight').value) || 0;
const riderWeight = parseFloat(document.getElementById('riderWeight').value) || 0;
const wheelSize = parseFloat(document.getElementById('wheelSize').value) || 0;
const tireWidth = parseFloat(document.getElementById('tireWidth').value) || 0;
const bikeType = document.getElementById('bikeType').value;
const totalWeight = bikeWeight + riderWeight;
const wheelDiameter = wheelSize + 2 * tireWidth; // mm
const wheelCircumference = Math.PI * wheelDiameter / 1000; // metry
// Średnie ważone parametry trasy
let totalDistance = 0, weightedSlope = 0, terrainFactor = 0;
rows.forEach(row => {
const d = parseFloat(row.dataset.distance);
const slope = parseFloat(row.dataset.slope);
const terr = row.dataset.terrain;
totalDistance += d;
weightedSlope += d * slope;
if (terr === 'flat') terrainFactor += d * 1.0;
if (terr === 'hilly') terrainFactor += d * 1.2;
if (terr === 'steep') terrainFactor += d * 1.5;
});
const avgSlope = weightedSlope / totalDistance;
const avgTerrainFactor = terrainFactor / totalDistance;
// prosty model przełożenia
let basePreference = 2.7;
basePreference /= (1 + avgSlope/10);
basePreference /= avgTerrainFactor;
// korekta wagą
if (totalWeight > 100) basePreference *= 0.9;
else if (totalWeight < 70) basePreference *= 1.05;
// korekta typem roweru
if (bikeType === 'road') basePreference *= 1.05;
if (bikeType === 'urban') basePreference *= 0.95;
// uwzględnienie obwodu koła – lekkie skalowanie
basePreference *= (wheelCircumference / 2.1); // normalizacja względem ~2.1 m (700x28)
let rearTeeth = Math.round(frontTeeth / basePreference);
rearTeeth = Math.max(11, Math.min(40, rearTeeth));
const suggestions = [11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30,32,34,36,38,40];
let nearest = suggestions.reduce((a,b) => Math.abs(b - rearTeeth) < Math.abs(a - rearTeeth) ? b : a);
document.getElementById('result').innerText = `Optymalna tylna zębatka: ${rearTeeth} — sugerowany wolnobieg: ${nearest}`;
document.getElementById('details').style.display = 'block';
document.getElementById('details').innerText = `Średnie nachylenie: ${avgSlope.toFixed(1)}%\nŚredni współczynnik terenu: ${avgTerrainFactor.toFixed(2)}\nObwód koła: ${wheelCircumference.toFixed(2)} m\nWaga całkowita: ${totalWeight.toFixed(1)} kg`;
}
// Eventy
document.getElementById('addSegmentBtn').addEventListener('click', () => addSegment());
document.getElementById('loadDefaultSegments').addEventListener('click', () => loadDefaultSegments());
document.getElementById('clearSegments').addEventListener('click', () => clearSegments());
document.getElementById('calcBtn').addEventListener('click', () => calculateGear());
// Na start przykładowa trasa
loadDefaultSegments();
</script>
Kod po stronie serwera
Informacja
Aplikacja nie korzysta z kodu po stronie serwera.