how do you calculate pitch of a roof? Roof Rafter Calculator
Last updated: December
Roof Pitch & Rafter Length Calculator
Rafter Calculator
Roof Pitch Calculator
Unit System
Imperial (Feet/Inches)
Metric (Meters/Centimeters)
Roof Dimensions
30 ft
6:12
1 ft
1.5 in
Roof Type & Configuration
Gable Roof
Hip Roof
Shed Roof
Gambrel Roof
16 in
Birdsmouth Cut
Roof Pitch Calculator
Rise & Run
Angle
26.57°
Pitch Calculation Results
Pitch Ratio:
6:12
Angle in Degrees:
26.57°
Percentage Grade:
50.0%
Slope Factor:
1.118
Common Pitch Name:
Conventional
2D Roof Visualization
Select a view angle to better understand your roof design.
Front View
Top View
Side View
Front view showing the roof elevation with pitch angle.
Rafter Calculation Results
Rafter Run:
15 ft
Rafter Length:
16.16 ft
Total Rafter Length:
17.16 ft
Number of Rafters:
24
Ridge Board Length:
30 ft
Plumb Cut Angle:
26.57°
Seat Cut Angle:
63.43°
Cutting Details
Birdsmouth Depth:
3.5 in
Heel Height:
1.5 in
Tail Cut Length:
12 in
Rafter Overhang:
1 ft
Note: These measurements assume standard framing practices. Always verify with local building codes and consult with a structural engineer for critical projects.
Common Roof Pitches
Typical Roof Pitches:
Flat Roof: 1:12 to 2:12 (4.8° to 9.5°)
Low Slope: 3:12 to 4:12 (14.0° to 18.4°)
Conventional: 5:12 to 9:12 (22.6° to 36.9°)
Steep Slope: 10:12 to 12:12 (39.8° to 45.0°)
Very Steep: 13:12 and above (47.3°+)
Common Rafter Spacing:
12 inches: Heavy snow loads
16 inches: Standard residential
19.2 inches: Energy-efficient framing
24 inches: Light loads, commercial
Always check local building codes for specific requirements.
Note: These measurements assume standard framing practices. Always verify with local building codes and consult with a structural engineer for critical projects.
Estimates are for planning purposes only. Consult with professionals for exact measurements.
`;
// Open print window
const printWindow = window.open('', '_blank');
printWindow.document.write(printContent);
printWindow.document.close();
printWindow.focus();
// Wait for content to load, then print
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 250);
showToast('Opening print preview...');
}
// Reset the calculator to default values
function resetCalculator() {
if (confirm('Are you sure you want to reset all values to default?')) {
setDefaults();
updateSliderDisplay();
if (currentMode === 'rafter') {
calculate();
} else {
calculatePitch();
}
showToast('Calculator reset to default values.');
}
}
// Save results as PDF
function saveAsPDF() {
// Check if jsPDF is available
if (typeof window.jspdf === 'undefined') {
showToast('PDF generation library not loaded. Please try again.');
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// Set document properties
doc.setProperties({
title: `${currentMode === 'rafter' ? 'Rafter' : 'Roof Pitch'} Calculation Results`,
subject: 'Roof framing calculations',
author: 'Roof & Rafter Calculator',
keywords: 'roof, rafter, pitch, calculation, framing',
creator: 'Roof & Rafter Calculator'
});
// Add title
doc.setFontSize(20);
doc.setTextColor(139, 69, 19);
doc.text(`${currentMode === 'rafter' ? 'Rafter' : 'Roof Pitch'} Calculation Results`, 105, 20, null, null, 'center');
// Add generation date
doc.setFontSize(10);
doc.setTextColor(100, 100, 100);
doc.text(`Generated on: ${new Date().toLocaleString()}`, 105, 30, null, null, 'center');
if (currentMode === 'rafter') {
// Add roof specifications
doc.setFontSize(14);
doc.setTextColor(0, 0, 0);
doc.text('Roof Specifications', 20, 45);
doc.setFontSize(11);
doc.text(`Roof Span: ${spanFt.toFixed(1)} ft`, 30, 55);
doc.text(`Roof Pitch: ${degreesToPitchRatio(pitchDegrees).toFixed(1)}:12 (${pitchDegrees.toFixed(1)}°)`, 30, 62);
doc.text(`Roof Type: ${currentRoofType.charAt(0).toUpperCase() + currentRoofType.slice(1)}`, 30, 69);
doc.text(`Overhang: ${overhangFt.toFixed(1)} ft`, 30, 76);
// Add rafter calculations
doc.setFontSize(14);
doc.text('Rafter Calculations', 20, 90);
doc.setFontSize(11);
doc.text(`Rafter Run: ${rafterCalc.run.toFixed(2)} ft`, 30, 100);
doc.text(`Rafter Length: ${rafterCalc.rafterLength.toFixed(2)} ft`, 30, 107);
doc.text(`Total Rafter Length: ${rafterCalc.totalLength.toFixed(2)} ft`, 30, 114);
doc.text(`Number of Rafters: ${numRafters}`, 30, 121);
doc.text(`Ridge Board Length: ${ridgeLength.toFixed(2)} ft`, 30, 128);
// Add cutting details
doc.setFontSize(14);
doc.text('Cutting Details', 20, 145);
doc.setFontSize(11);
doc.text(`Plumb Cut Angle: ${plumbCutAngle.toFixed(2)}°`, 30, 155);
doc.text(`Seat Cut Angle: ${seatCutAngle.toFixed(2)}°`, 30, 162);
doc.text(`Birdsmouth Depth: ${seatDepthIn.toFixed(1)} in`, 30, 169);
doc.text(`Heel Height: ${heelHeightIn.toFixed(1)} in`, 30, 176);
doc.text(`Tail Cut Length: ${tailCutLength.toFixed(1)} in`, 30, 183);
} else {
// Add pitch specifications
doc.setFontSize(14);
doc.setTextColor(0, 0, 0);
doc.text('Roof Pitch Specifications', 20, 45);
doc.setFontSize(11);
doc.text(`Calculation Method: ${currentPitchFrom === 'rise-run' ? 'Rise & Run' : 'Angle'}`, 30, 55);
if (currentPitchFrom === 'rise-run') {
doc.text(`Rise: ${riseValue} ${riseUnit}`, 30, 62);
doc.text(`Run: ${runValue} ${runUnit}`, 30, 69);
} else {
doc.text(`Angle: ${angleValue}°`, 30, 62);
}
// Add pitch results
doc.setFontSize(14);
doc.text('Pitch Calculation Results', 20, 85);
doc.setFontSize(11);
doc.text(`Pitch Ratio: ${pitchResult.ratio}:12`, 30, 95);
doc.text(`Angle in Degrees: ${pitchResult.degrees}°`, 30, 102);
doc.text(`Percentage Grade: ${pitchResult.percent}%`, 30, 109);
doc.text(`Slope Factor: ${pitchResult.slopeFactor.toFixed(3)}`, 30, 116);
doc.text(`Pitch Type: ${pitchName}`, 30, 123);
}
// Add notes
doc.setFontSize(10);
doc.setTextColor(100, 100, 100);
doc.text('Note: These measurements assume standard framing practices.', 20, currentMode === 'rafter' ? 200 : 150);
doc.text('Always verify with local building codes and consult with', 20, currentMode === 'rafter' ? 207 : 157);
doc.text('a structural engineer for critical projects.', 20, currentMode === 'rafter' ? 214 : 164);
doc.text('Estimates are for planning purposes only.', 20, currentMode === 'rafter' ? 221 : 171);
// Add footer
doc.setFontSize(8);
doc.text('Roof & Rafter Calculator', 105, 280, null, null, 'center');
// Save the PDF
doc.save(`${currentMode === 'rafter' ? 'rafter' : 'pitch'}-calculator-results-${new Date().toISOString().slice(0, 10)}.pdf`);
showToast('PDF saved successfully!');
}
// Global variables for calculation results
let spanFt, pitchDegrees, overhangFt, rafterCalc, numRafters, ridgeLength;
let plumbCutAngle, seatCutAngle, seatDepthIn, heelHeightIn, tailCutLength;
let pitchResult, pitchName;
let riseValue, riseUnit, runValue, runUnit, angleValue;
// Calculate rafter
function calculateRafter() {
// Get and validate values
const spanResult = validateNumber(elements.buildingSpan, 5, 200);
const pitchResult = validateNumber(elements.roofPitch, 0, 100);
const overhangResult = validateNumber(elements.overhang, 0, 10);
const ridgeResult = validateNumber(elements.ridgeThickness, 0.5, 6);
const seatDepthResult = validateNumber(elements.seatDepth, 1, 6);
const heelHeightResult = validateNumber(elements.heelHeight, 0.5, 4);
const spacingResult = validateNumber(elements.rafterSpacing, 12, 48);
// Check if any validation failed
if (!spanResult.valid || !pitchResult.valid || !overhangResult.valid ||
!ridgeResult.valid || !seatDepthResult.valid || !heelHeightResult.valid ||
!spacingResult.valid) {
return;
}
// Get values
const spanValue = spanResult.value || 0;
const pitchValue = pitchResult.value || 0;
const overhangValue = overhangResult.value || 0;
const ridgeValue = ridgeResult.value || 0;
const seatDepthValue = seatDepthResult.value || 0;
const heelHeightValue = heelHeightResult.value || 0;
const spacingValue = spacingResult.value || 0;
const spanUnit = elements.spanUnit.value;
const pitchUnit = elements.pitchUnit.value;
const overhangUnit = elements.overhangUnit.value;
const ridgeUnit = elements.ridgeUnit.value;
const seatDepthUnit = elements.seatDepthUnit.value;
const heelHeightUnit = elements.heelHeightUnit.value;
const spacingUnit = elements.spacingUnit.value;
// Convert to consistent units
spanFt = convertToFeet(spanValue, spanUnit);
overhangFt = convertToFeet(overhangValue, overhangUnit);
const ridgeIn = convertToInches(ridgeValue, ridgeUnit);
seatDepthIn = convertToInches(seatDepthValue, seatDepthUnit);
heelHeightIn = convertToInches(heelHeightValue, heelHeightUnit);
const spacingIn = convertToInches(spacingValue, spacingUnit);
// Calculate pitch in degrees
pitchDegrees = pitchToDegrees(pitchValue, pitchUnit);
// Calculate rafter dimensions
rafterCalc = calculateRafterLength(spanFt, pitchDegrees, overhangFt, ridgeIn);
// Calculate number of rafters
numRafters = Math.ceil(spanFt * 12 / spacingIn) + 1;
// Calculate ridge board length
ridgeLength = 0;
if (currentRoofType === 'gable' || currentRoofType === 'gambrel') {
ridgeLength = spanFt;
} else if (currentRoofType === 'hip') {
// Hip roof has shorter ridge
ridgeLength = spanFt * 0.6;
}
// Calculate cutting angles
plumbCutAngle = pitchDegrees;
seatCutAngle = 90 - pitchDegrees;
// Calculate tail cut length (simplified)
tailCutLength = overhangFt * 12;
// Update display
elements.rafterRun.textContent = `${rafterCalc.run.toFixed(2)} ft`;
elements.rafterLength.textContent = `${rafterCalc.rafterLength.toFixed(2)} ft`;
elements.rafterTotal.textContent = `${rafterCalc.totalLength.toFixed(2)} ft`;
elements.totalRafters.textContent = numRafters;
elements.ridgeLength.textContent = `${ridgeLength.toFixed(2)} ft`;
elements.plumbCutAngle.textContent = `${plumbCutAngle.toFixed(2)}°`;
elements.seatCutAngle.textContent = `${seatCutAngle.toFixed(2)}°`;
elements.birdsmouthDepth.textContent = `${seatDepthIn.toFixed(1)} in`;
elements.heelHeightResult.textContent = `${heelHeightIn.toFixed(1)} in`;
elements.tailCutLength.textContent = `${tailCutLength.toFixed(1)} in`;
elements.rafterOverhang.textContent = `${overhangFt.toFixed(1)} ft`;
// Update visualization
createRoofVisualization(spanFt, pitchDegrees, currentRoofType, rafterCalc);
}
// Calculate pitch
function calculatePitch() {
if (currentPitchFrom === 'rise-run') {
// Get rise and run values
const riseResult = validateNumber(elements.riseValue, 0, 1000);
const runResult = validateNumber(elements.runValue, 0, 1000);
if (!riseResult.valid || !runResult.valid) return;
riseValue = riseResult.value || 0;
runValue = runResult.value || 0;
riseUnit = elements.riseUnit.value;
runUnit = elements.runUnit.value;
// Convert to inches for calculation
const riseIn = convertToInches(riseValue, riseUnit);
const runIn = convertToInches(runValue, runUnit);
if (runIn === 0) {
showToast('Run cannot be zero');
return;
}
// Calculate pitch
pitchResult = calculatePitchFromRiseRun(riseIn, runIn);
} else {
// Get angle value
const angleResult = validateNumber(elements.angleValue, 0, 90);
if (!angleResult.valid) return;
angleValue = angleResult.value || 0;
// Calculate pitch
pitchResult = calculatePitchFromAngle(angleValue);
}
// Get pitch name
pitchName = getPitchName(pitchResult.ratio);
// Update display
elements.pitchRatioResult.textContent = `${pitchResult.ratio}:12`;
elements.pitchAngleResult.textContent = `${pitchResult.degrees}°`;
elements.pitchPercentResult.textContent = `${pitchResult.percent}%`;
elements.slopeFactorResult.textContent = pitchResult.slopeFactor.toFixed(3);
elements.pitchNameResult.textContent = pitchName;
// Update visualization
createPitchVisualization(pitchResult.ratio, pitchResult.degrees);
// Also update rafter calculator pitch if in rafter mode
if (currentMode === 'rafter') {
elements.roofPitch.value = pitchResult.ratio.toFixed(1);
elements.pitchSlider.value = pitchResult.ratio;
updateSliderDisplay();
calculateRafter();
}
}
// Main calculation function
function calculate() {
if (currentMode === 'rafter') {
calculateRafter();
} else {
calculatePitch();
}
}
// Setup event listeners
function setupEventListeners() {
// Mode switching
document.querySelectorAll('.mode-option').forEach(option => {
option.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.mode-option').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the mode
currentMode = this.dataset.mode;
// Show/hide tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.getElementById(`${currentMode}-tab`).classList.add('active');
// Show/hide results sections
const rafterResults = document.getElementById('rafter-results');
const cuttingDetails = document.getElementById('cutting-details');
if (currentMode === 'rafter') {
rafterResults.style.display = 'block';
cuttingDetails.style.display = 'block';
} else {
rafterResults.style.display = 'none';
cuttingDetails.style.display = 'none';
}
// Recalculate
calculate();
});
});
// View switching
document.querySelectorAll('.view-tab').forEach(tab => {
tab.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.view-tab').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the view
currentView = this.dataset.view;
// Recalculate and update visualization
if (currentMode === 'rafter') {
calculateRafter();
}
});
});
// Unit system options
document.querySelectorAll('.unit-option[data-unit]').forEach(option => {
option.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.unit-option[data-unit]').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the unit system
currentUnitSystem = this.dataset.unit;
// Update units based on system
if (currentUnitSystem === 'metric') {
elements.spanUnit.value = 'm';
elements.overhangUnit.value = 'm';
elements.ridgeUnit.value = 'cm';
elements.seatDepthUnit.value = 'cm';
elements.heelHeightUnit.value = 'cm';
elements.spacingUnit.value = 'cm';
elements.riseUnit.value = 'cm';
elements.runUnit.value = 'cm';
} else {
elements.spanUnit.value = 'ft';
elements.overhangUnit.value = 'ft';
elements.ridgeUnit.value = 'in';
elements.seatDepthUnit.value = 'in';
elements.heelHeightUnit.value = 'in';
elements.spacingUnit.value = 'in';
elements.riseUnit.value = 'in';
elements.runUnit.value = 'in';
}
updateSliderDisplay();
calculate();
});
});
// Pitch from options
document.querySelectorAll('.unit-option[data-pitch-from]').forEach(option => {
option.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.unit-option[data-pitch-from]').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the pitch from method
currentPitchFrom = this.dataset.pitchFrom;
togglePitchInputs();
calculate();
});
});
// Roof type options
document.querySelectorAll('.calc-option-tile').forEach(tile => {
tile.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.calc-option-tile').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the roof type value
currentRoofType = this.dataset.value;
calculate();
});
});
// Action buttons
elements.copyBtn.addEventListener('click', copyResultsToClipboard);
elements.printBtn.addEventListener('click', printResults);
elements.resetBtn.addEventListener('click', resetCalculator);
elements.saveBtn.addEventListener('click', saveAsPDF);
// Rafter calculator inputs
elements.buildingSpan.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 5, 200);
if (result.valid) {
if (result.value !== '') {
elements.spanSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'rafter') calculate();
}
});
elements.spanSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.buildingSpan.value = this.value;
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.spanUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.roofPitch.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 100);
if (result.valid) {
if (result.value !== '') {
elements.pitchSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'rafter') calculate();
}
});
elements.pitchSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.roofPitch.value = this.value;
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.pitchUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.overhang.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 10);
if (result.valid) {
if (result.value !== '') {
elements.overhangSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'rafter') calculate();
}
});
elements.overhangSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.overhang.value = this.value;
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.overhangUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.ridgeThickness.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0.5, 6);
if (result.valid) {
if (result.value !== '') {
elements.ridgeSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'rafter') calculate();
}
});
elements.ridgeSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.ridgeThickness.value = this.value;
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
elements.ridgeUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
// Birdsmouth toggle
elements.birdsmouthCut.addEventListener('change', function(e) {
e.stopPropagation();
toggleBirdsmouthOptions();
});
// Seat depth
elements.seatDepth.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 1, 6);
if (result.valid && currentMode === 'rafter') calculate();
});
// Heel height
elements.heelHeight.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0.5, 4);
if (result.valid && currentMode === 'rafter') calculate();
});
// Rafter spacing
elements.rafterSpacing.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 12, 48);
if (result.valid) {
if (result.value !== '') {
elements.spacingSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'rafter') calculate();
}
});
elements.spacingSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.rafterSpacing.value = this.value;
updateSliderDisplay();
if (currentMode === 'rafter') calculate();
});
// Pitch calculator inputs
elements.riseValue.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 1000);
if (result.valid && currentMode === 'pitch') calculatePitch();
});
elements.runValue.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 1000);
if (result.valid && currentMode === 'pitch') calculatePitch();
});
elements.angleValue.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 90);
if (result.valid) {
if (result.value !== '') {
elements.angleSlider.value = result.value;
updateSliderDisplay();
}
if (currentMode === 'pitch') calculatePitch();
}
});
elements.angleSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.angleValue.value = this.value;
updateSliderDisplay();
if (currentMode === 'pitch') calculatePitch();
});
// Window resize for visualization
debouncedCalculateFn = debounce(calculate, 250);
window.addEventListener('resize', debouncedCalculateFn);
}
// Debounce function for performance
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Initialize when ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();