Wood Fence Calculator
Cost Calculation
Wood Fence 2D Visualization
Real-time visual representation of your fence design with scale indicators.
Number of Panels:
6 panels
Materials & Cost Summary
Linear Feet of Fencing:
50 LF
Number of Pickets/Boards:
300
Installation Cost:
$600.00
Total Estimated Cost:
$1,500.00
Note: This estimate includes materials and basic installation. Additional costs may include permits, removal of old fence, and site preparation.
Common Fence Prices
Typical Prices per Linear Foot (Material Only):
- Pressure Treated Pine: $12 - $25
- Cedar Privacy: $18 - $35
- Redwood: $28 - $45
- Vinyl: $25 - $40
- Chain Link: $10 - $20
Installation Costs:
- Basic Installation: $10 - $15 per LF
- Premium Installation: $15 - $25 per LF
- Gate Installation: $150 - $400 per gate
Prices vary by region, height, style, and wood grade.
Wood Fence Calculation Results
Generated on: ${new Date().toLocaleString()}
Fence Specifications
Fence Length:
${elements.visualLength.textContent}
Fence Height:
${elements.visualHeight.textContent}
Fence Style:
${currentFenceStyle.charAt(0).toUpperCase() + currentFenceStyle.slice(1)}
Wood Type:
${currentWoodType.charAt(0).toUpperCase() + currentWoodType.slice(1)}
Materials Required
Linear Feet of Fencing:
${elements.totalLinearFeet.textContent}
Number of Posts:
${elements.totalPosts.textContent}
Number of Rails:
${elements.totalRails.textContent}
Number of Pickets/Boards:
${elements.totalBoards.textContent}
Cost Summary
Material Cost:
${elements.materialCost.textContent}
Installation Cost:
${elements.installationCostTotal.textContent}
Total Estimated Cost:
${elements.totalCost.textContent}
Note: This estimate includes materials and basic installation. Additional costs may include permits, removal of old fence, and site preparation.
Estimates are for planning purposes only. Consult with professionals for exact measurements and costs.
`;
// 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();
calculate();
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: 'Wood Fence Calculation Results',
subject: 'Fence materials and cost estimate',
author: 'Wood Fence Calculator',
keywords: 'fence, wood, calculation, estimate',
creator: 'Wood Fence Calculator'
});
// Add title
doc.setFontSize(20);
doc.setTextColor(46, 125, 50);
doc.text('Wood Fence 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');
// Add fence specifications
doc.setFontSize(14);
doc.setTextColor(0, 0, 0);
doc.text('Fence Specifications', 20, 45);
doc.setFontSize(11);
doc.text(`Fence Length: ${elements.visualLength.textContent}`, 30, 55);
doc.text(`Fence Height: ${elements.visualHeight.textContent}`, 30, 62);
doc.text(`Fence Style: ${currentFenceStyle.charAt(0).toUpperCase() + currentFenceStyle.slice(1)}`, 30, 69);
doc.text(`Wood Type: ${currentWoodType.charAt(0).toUpperCase() + currentWoodType.slice(1)}`, 30, 76);
// Add materials required
doc.setFontSize(14);
doc.text('Materials Required', 20, 90);
doc.setFontSize(11);
doc.text(`Linear Feet of Fencing: ${elements.totalLinearFeet.textContent}`, 30, 100);
doc.text(`Number of Posts: ${elements.totalPosts.textContent}`, 30, 107);
doc.text(`Number of Rails: ${elements.totalRails.textContent}`, 30, 114);
doc.text(`Number of Pickets/Boards: ${elements.totalBoards.textContent}`, 30, 121);
// Add cost summary
doc.setFontSize(14);
doc.text('Cost Summary', 20, 135);
doc.setFontSize(11);
doc.text(`Material Cost: ${elements.materialCost.textContent}`, 30, 145);
doc.text(`Installation Cost: ${elements.installationCostTotal.textContent}`, 30, 152);
doc.setFontSize(12);
doc.setTextColor(211, 47, 47);
doc.text(`Total Estimated Cost: ${elements.totalCost.textContent}`, 30, 162);
// Add notes
doc.setFontSize(10);
doc.setTextColor(100, 100, 100);
doc.text('Note: This estimate includes materials and basic installation.', 20, 180);
doc.text('Additional costs may include permits, removal of old fence,', 20, 187);
doc.text('and site preparation. Estimates are for planning purposes only.', 20, 194);
doc.text('Consult with professionals for exact measurements and costs.', 20, 201);
// Add footer
doc.setFontSize(8);
doc.text('Wood Fence Calculator - https://example.com/fence-calculator', 105, 280, null, null, 'center');
// Save the PDF
doc.save(`fence-calculator-results-${new Date().toISOString().slice(0, 10)}.pdf`);
showToast('PDF saved successfully!');
}
// Setup event listeners
function setupEventListeners() {
// Action buttons
elements.copyBtn.addEventListener('click', copyResultsToClipboard);
elements.printBtn.addEventListener('click', printResults);
elements.resetBtn.addEventListener('click', resetCalculator);
elements.saveBtn.addEventListener('click', saveAsPDF);
// Fence style 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 fence style value
currentFenceStyle = this.dataset.value;
calculate();
});
});
// Wood type options
document.querySelectorAll('.calc-material-option').forEach(option => {
option.addEventListener('click', function(e) {
e.stopPropagation();
// Remove active class from all options
document.querySelectorAll('.calc-material-option').forEach(opt => {
opt.classList.remove('active');
});
// Add active class to clicked option
this.classList.add('active');
// Set the wood type value
currentWoodType = this.dataset.value;
// Update price per linear foot based on wood type
const basePrice = woodTypePrices[currentWoodType];
elements.pricePerLF.value = basePrice.toFixed(2);
elements.priceSlider.value = basePrice;
updateSliderDisplay();
calculate();
});
});
// Length input and slider sync
elements.fenceLength.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 1, 500);
if (result.valid) {
if (result.value !== '') {
elements.lengthSlider.value = result.value;
updateSliderDisplay();
}
calculate();
}
});
elements.lengthSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.fenceLength.value = this.value;
updateSliderDisplay();
calculate();
});
elements.lengthUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
calculate();
});
// Height input and slider sync
elements.fenceHeight.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 1, 12);
if (result.valid) {
if (result.value !== '') {
elements.heightSlider.value = result.value;
updateSliderDisplay();
}
calculate();
}
});
elements.heightSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.fenceHeight.value = this.value;
updateSliderDisplay();
calculate();
});
elements.heightUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
calculate();
});
// Gate toggle
elements.includeGates.addEventListener('change', function(e) {
e.stopPropagation();
toggleGateOptions();
});
// Gate count input and slider sync
elements.gateCount.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 10);
if (result.valid) {
if (result.value !== '') {
elements.gateSlider.value = result.value;
updateSliderDisplay();
}
calculate();
}
});
elements.gateSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.gateCount.value = this.value;
updateSliderDisplay();
calculate();
});
// Gate width
elements.gateWidth.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 1, 10);
if (result.valid) {
calculate();
}
});
elements.gateWidthUnit.addEventListener('change', function(e) {
e.stopPropagation();
calculate();
});
// Post spacing input and slider sync
elements.postSpacing.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 4, 12);
if (result.valid) {
if (result.value !== '') {
elements.postSpacingSlider.value = result.value;
updateSliderDisplay();
}
calculate();
}
});
elements.postSpacingSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.postSpacing.value = this.value;
updateSliderDisplay();
calculate();
});
elements.postSpacingUnit.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
calculate();
});
// Price input and slider sync
elements.pricePerLF.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 1, 100);
if (result.valid) {
if (result.value !== '') {
elements.priceSlider.value = result.value;
updateSliderDisplay();
}
calculate();
}
});
elements.priceSlider.addEventListener('input', function(e) {
e.stopPropagation();
elements.pricePerLF.value = this.value;
updateSliderDisplay();
calculate();
});
// Currency change
elements.currency.addEventListener('change', function(e) {
e.stopPropagation();
updateSliderDisplay();
calculate();
});
// Installation toggle
elements.includeInstallation.addEventListener('change', function(e) {
e.stopPropagation();
toggleInstallationOptions();
});
// Installation cost
elements.installationCost.addEventListener('input', function(e) {
e.stopPropagation();
const result = validateNumber(this, 0, 50);
if (result.valid) {
calculate();
}
});
// Window resize for visualization
debouncedCalculateFn = debounce(calculate, 250);
window.addEventListener('resize', debouncedCalculateFn);
}
// Convert any unit to feet
function convertToFeet(value, unit) {
return value * (unitToFeet[unit] || 1);
}
// 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);
};
}
// Create real-time fence visualization with scale and detailed styles
function createFenceVisualization(lengthFt, heightFt, postSpacingFt, gateCount, gateWidthFt) {
elements.visualizationContainer.innerHTML = '';
// Calculate number of posts
const numPosts = Math.ceil(lengthFt / postSpacingFt) + 1;
// Calculate total gate width in feet
const totalGateWidthFt = gateCount * gateWidthFt;
// Calculate fence length without gates
const fenceLengthWithoutGates = lengthFt - totalGateWidthFt;
// Calculate number of panels between posts (excluding gate areas)
const numPanels = numPosts - 1;
// Set container dimensions
const containerWidth = elements.visualizationContainer.clientWidth;
const containerHeight = elements.visualizationContainer.clientHeight;
// Calculate scaling factor
const scale = Math.min(containerWidth / (lengthFt * 12), containerHeight / (heightFt * 20));
// Calculate visual dimensions
const visualWidth = lengthFt * 12 * scale;
const visualHeight = heightFt * 20 * scale;
// Create fence visual container
const fenceVisual = document.createElement('div');
fenceVisual.className = 'fence-visual';
fenceVisual.style.width = `${visualWidth}px`;
fenceVisual.style.height = `${visualHeight}px`;
// Create ground line
const groundLine = document.createElement('div');
groundLine.style.position = 'absolute';
groundLine.style.bottom = '0';
groundLine.style.left = '0';
groundLine.style.width = '100%';
groundLine.style.height = '5px';
groundLine.style.backgroundColor = '#8B4513';
fenceVisual.appendChild(groundLine);
// Create scale rulers
// Horizontal scale (length)
const horizontalScale = document.createElement('div');
horizontalScale.className = 'scale-ruler horizontal-scale';
horizontalScale.style.width = `${visualWidth}px`;
horizontalScale.style.left = '0';
horizontalScale.textContent = `${lengthFt} ft`;
fenceVisual.appendChild(horizontalScale);
// Vertical scale (height)
const verticalScale = document.createElement('div');
verticalScale.className = 'scale-ruler vertical-scale';
verticalScale.style.height = `${visualHeight}px`;
verticalScale.style.top = '0';
verticalScale.textContent = `${heightFt} ft`;
fenceVisual.appendChild(verticalScale);
// Calculate post positions
const postPositions = [];
let currentPosition = 0;
// Distribute posts evenly, accounting for gates
for (let i = 0; i < numPosts; i++) {
// If we have gates and we're at a gate position, skip ahead
if (gateCount > 0 && i > 0 && i < gateCount + 1) {
currentPosition += gateWidthFt;
}
postPositions.push(currentPosition);
currentPosition += postSpacingFt;
}
// Adjust last position to match total length
if (postPositions.length > 1) {
const lastPosition = postPositions[postPositions.length - 1];
if (lastPosition < lengthFt) {
// Adjust spacing to fill the length
const adjustment = (lengthFt - lastPosition) / (numPosts - 1);
for (let i = 1; i < postPositions.length; i++) {
postPositions[i] += adjustment * i;
}
}
}
// Create posts
for (let i = 0; i < postPositions.length; i++) {
const postPosition = postPositions[i];
const isGatePost = (i > 0 && i <= gateCount);
const post = document.createElement('div');
post.className = 'fence-post';
post.style.width = `${Math.max(8, 15 * scale)}px`;
post.style.height = `${visualHeight * 0.95}px`;
post.style.left = `${(postPosition / lengthFt) * 100}%`;
if (isGatePost) {
post.style.backgroundColor = '#5d4037';
post.style.borderColor = '#3e2723';
post.style.borderWidth = '2px';
}
fenceVisual.appendChild(post);
}
// Create fence panels between posts based on style
for (let i = 0; i < postPositions.length - 1; i++) {
const startPos = postPositions[i];
const endPos = postPositions[i + 1];
const panelWidth = endPos - startPos;
// Check if this is a gate panel
const isGatePanel = (i < gateCount);
if (!isGatePanel) {
// Create fence panel container
const panel = document.createElement('div');
panel.style.width = `${(panelWidth / lengthFt) * 100}%`;
panel.style.height = `${visualHeight * 0.8}px`;
panel.style.left = `${(startPos / lengthFt) * 100}%`;
panel.style.bottom = '5px';
panel.style.position = 'absolute';
panel.style.overflow = 'hidden';
if (currentFenceStyle === 'picket') {
// Create picket fence
const picketWidth = 2 * scale;
const picketSpacing = 3 * scale;
const numPickets = Math.floor((panelWidth * 12 * scale) / (picketWidth + picketSpacing));
for (let p = 0; p < numPickets; p++) {
const picket = document.createElement('div');
picket.className = 'fence-picket';
picket.style.width = `${picketWidth}px`;
picket.style.height = `${visualHeight * 0.7}px`;
picket.style.left = `${p * (picketWidth + picketSpacing)}px`;
picket.style.borderRadius = '3px 3px 0 0';
// Add decorative top to picket
picket.style.clipPath = 'polygon(0% 0%, 100% 0%, 100% 90%, 50% 100%, 0% 90%)';
panel.appendChild(picket);
}
// Add rails
for (let r = 0; r < 2; r++) {
const rail = document.createElement('div');
rail.className = 'fence-rail';
rail.style.width = '100%';
rail.style.height = `${5 * scale}px`;
rail.style.bottom = `${20 + r * 30}px`;
rail.style.position = 'absolute';
rail.style.borderRadius = '2px';
panel.appendChild(rail);
}
} else if (currentFenceStyle === 'privacy') {
// Create privacy fence (solid boards)
panel.style.backgroundColor = '#8d6e63';
panel.style.border = '1px solid #5d4037';
panel.style.borderRadius = '3px';
// Add board texture with vertical lines
const boardWidth = 4 * scale;
const numBoards = Math.floor((panelWidth * 12 * scale) / boardWidth);
for (let b = 0; b < numBoards; b++) {
const board = document.createElement('div');
board.style.position = 'absolute';
board.style.width = `${boardWidth}px`;
board.style.height = '100%';
board.style.left = `${b * boardWidth}px`;
board.style.backgroundColor = 'rgba(0,0,0,0.1)';
board.style.borderRight = '1px solid rgba(0,0,0,0.2)';
panel.appendChild(board);
}
// Add rails
for (let r = 0; r < 3; r++) {
const rail = document.createElement('div');
rail.className = 'fence-rail';
rail.style.width = '100%';
rail.style.height = `${6 * scale}px`;
rail.style.bottom = `${15 + r * 25}px`;
rail.style.position = 'absolute';
rail.style.backgroundColor = '#5d4037';
rail.style.borderRadius = '2px';
panel.appendChild(rail);
}
} else if (currentFenceStyle === 'split-rail') {
// Create split-rail fence
panel.style.backgroundColor = 'transparent';
// Add rails (horizontal)
for (let r = 0; r < 2; r++) {
const rail = document.createElement('div');
rail.className = 'fence-rail';
rail.style.width = '100%';
rail.style.height = `${8 * scale}px`;
rail.style.bottom = `${30 + r * 40}px`;
rail.style.position = 'absolute';
rail.style.backgroundColor = '#8d6e63';
rail.style.border = '2px solid #5d4037';
rail.style.borderRadius = '4px';
// Add rustic texture
rail.style.backgroundImage = 'linear-gradient(90deg, transparent 90%, rgba(0,0,0,0.1) 100%)';
panel.appendChild(rail);
}
// Add vertical posts between rails for authentic look
const postSpacingVisual = 40 * scale;
const numVisualPosts = Math.floor((panelWidth * 12 * scale) / postSpacingVisual);
for (let p = 0; p < numVisualPosts; p++) {
const visualPost = document.createElement('div');
visualPost.style.position = 'absolute';
visualPost.style.width = `${6 * scale}px`;
visualPost.style.height = `${visualHeight * 0.5}px`;
visualPost.style.left = `${p * postSpacingVisual}px`;
visualPost.style.bottom = '30px';
visualPost.style.backgroundColor = '#5d4037';
visualPost.style.borderRadius = '3px';
panel.appendChild(visualPost);
}
} else if (currentFenceStyle === 'lattice') {
// Create lattice fence
panel.style.backgroundColor = '#d7ccc8';
panel.style.border = '1px solid #a1887f';
panel.style.borderRadius = '3px';
// Create lattice pattern
const latticeSize = 20 * scale;
const numHorizontal = Math.ceil(visualHeight * 0.8 / latticeSize);
const numVertical = Math.ceil((panelWidth * 12 * scale) / latticeSize);
// Horizontal lattice strips
for (let h = 0; h < numHorizontal; h++) {
const horizontal = document.createElement('div');
horizontal.className = 'fence-lattice-horizontal';
horizontal.style.top = `${h * latticeSize}px`;
horizontal.style.width = '100%';
horizontal.style.height = `${2 * scale}px`;
horizontal.style.backgroundColor = '#a1887f';
panel.appendChild(horizontal);
}
// Vertical lattice strips
for (let v = 0; v < numVertical; v++) {
const vertical = document.createElement('div');
vertical.className = 'fence-lattice-vertical';
vertical.style.left = `${v * latticeSize}px`;
vertical.style.height = '100%';
vertical.style.width = `${2 * scale}px`;
vertical.style.backgroundColor = '#a1887f';
panel.appendChild(vertical);
}
// Add border frame
const frame = document.createElement('div');
frame.style.position = 'absolute';
frame.style.width = '100%';
frame.style.height = '100%';
frame.style.border = '2px solid #8d6e63';
frame.style.boxSizing = 'border-box';
panel.appendChild(frame);
}
fenceVisual.appendChild(panel);
} else {
// Create gate
const gate = document.createElement('div');
gate.className = 'gate';
gate.style.width = `${(panelWidth / lengthFt) * 100}%`;
gate.style.height = `${visualHeight * 0.7}px`;
gate.style.left = `${(startPos / lengthFt) * 100}%`;
gate.style.bottom = '5px';
gate.textContent = 'GATE';
gate.style.fontSize = `${Math.max(10, 12 * scale)}px`;
fenceVisual.appendChild(gate);
}
}
elements.visualizationContainer.appendChild(fenceVisual);
// Update visual info
elements.visualLength.textContent = `${lengthFt.toFixed(1)} ft`;
elements.visualHeight.textContent = `${heightFt.toFixed(1)} ft`;
elements.visualPosts.textContent = `${numPosts} posts`;
elements.visualPanels.textContent = `${numPanels} panels`;
}
// Main calculation
function calculate() {
// Get and validate values
const lengthResult = validateNumber(elements.fenceLength, 1, 500);
const heightResult = validateNumber(elements.fenceHeight, 1, 12);
const gateCountResult = validateNumber(elements.gateCount, 0, 10);
const gateWidthResult = validateNumber(elements.gateWidth, 1, 10);
const postSpacingResult = validateNumber(elements.postSpacing, 4, 12);
const priceResult = validateNumber(elements.pricePerLF, 1, 100);
const installationResult = validateNumber(elements.installationCost, 0, 50);
// Check if any validation failed
if (!lengthResult.valid || !heightResult.valid || !gateCountResult.valid ||
!gateWidthResult.valid || !postSpacingResult.valid || !priceResult.valid ||
!installationResult.valid) {
return;
}
// Get values
const lengthValue = lengthResult.value || 0;
const heightValue = heightResult.value || 0;
const gateCount = elements.includeGates.checked ? (gateCountResult.value || 0) : 0;
const gateWidthValue = gateWidthResult.value || 0;
const postSpacingValue = postSpacingResult.value || 0;
const pricePerLF = priceResult.value || 0;
const installationCostPerLF = elements.includeInstallation.checked ? (installationResult.value || 0) : 0;
const currency = elements.currency.value;
const lengthUnit = elements.lengthUnit.value;
const heightUnit = elements.heightUnit.value;
const gateWidthUnit = elements.gateWidthUnit.value;
const postSpacingUnit = elements.postSpacingUnit.value;
// Convert to feet
const lengthFt = convertToFeet(lengthValue, lengthUnit);
const heightFt = convertToFeet(heightValue, heightUnit);
const gateWidthFt = convertToFeet(gateWidthValue, gateWidthUnit);
const postSpacingFt = convertToFeet(postSpacingValue, postSpacingUnit);
// Calculate total gate width
const totalGateWidthFt = gateCount * gateWidthFt;
// Calculate effective fence length (excluding gates)
const effectiveFenceLengthFt = Math.max(0, lengthFt - totalGateWidthFt);
// Calculate number of posts
const numPosts = Math.ceil(effectiveFenceLengthFt / postSpacingFt) + 1 + gateCount;
// Calculate number of panels
const numPanels = numPosts - 1;
// Calculate number of rails (typically 2 or 3 rails per panel)
const railsPerPanel = fenceStyles[currentFenceStyle].rails;
const numRails = numPanels * railsPerPanel;
// Calculate number of boards/pickets
const boardsPerFoot = fenceStyles[currentFenceStyle].boardsPerFoot;
const numBoards = Math.ceil(effectiveFenceLengthFt * boardsPerFoot);
// Calculate costs
const currencySymbol = currencySymbols[currency] || '$';
const materialCostValue = lengthFt * pricePerLF;
const installationCostValue = lengthFt * installationCostPerLF;
const totalCostValue = materialCostValue + installationCostValue;
// Update display
elements.totalLinearFeet.textContent = `${lengthFt.toFixed(1)} LF`;
elements.totalPosts.textContent = numPosts;
elements.totalRails.textContent = numRails;
elements.totalBoards.textContent = numBoards;
elements.materialCost.textContent = `${currencySymbol}${materialCostValue.toFixed(2)}`;
elements.installationCostTotal.textContent = `${currencySymbol}${installationCostValue.toFixed(2)}`;
elements.totalCost.textContent = `${currencySymbol}${totalCostValue.toFixed(2)}`;
// Update visualization
createFenceVisualization(lengthFt, heightFt, postSpacingFt, gateCount, gateWidthFt);
}
// Initialize when ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();