Created
April 6, 2025 23:03
-
-
Save tech-andgar/4a971b92925226cacc5c244f58fea481 to your computer and use it in GitHub Desktop.
Resizable split views native HTML / JS / CSS - ALL Vanilla
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Responsive Split View</title> | |
| <style> | |
| /* Reset and base styles */ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html, | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: Arial, sans-serif; | |
| overflow: hidden; | |
| height: 100%; | |
| width: 100%; | |
| } | |
| .container { | |
| display: flex; | |
| width: 100%; | |
| height: 100vh; | |
| /* Use viewport height to ensure full height */ | |
| flex-direction: row; | |
| /* Horizontal by default (for desktop) */ | |
| } | |
| .panel { | |
| overflow: auto; | |
| padding: 20px; | |
| box-sizing: border-box; | |
| } | |
| .left-panel { | |
| background-color: #f0f0f0; | |
| width: 50%; | |
| min-width: 100px; | |
| max-width: calc(100% - 108px); | |
| height: 100%; | |
| /* Ensure full height */ | |
| } | |
| .right-panel { | |
| background-color: #e0e0e0; | |
| flex-grow: 1; | |
| width: 50%; | |
| min-width: 100px; | |
| max-width: calc(100% - 108px); | |
| height: 100%; | |
| /* Ensure full height */ | |
| } | |
| .divider { | |
| width: 8px; | |
| background-color: #ccc; | |
| cursor: col-resize; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| transition: background-color 0.2s; | |
| flex-shrink: 0; | |
| height: 100%; | |
| /* Ensure full height */ | |
| } | |
| .divider:hover, | |
| .divider.active { | |
| background-color: #999; | |
| } | |
| .divider-handle { | |
| height: 30px; | |
| width: 4px; | |
| background-color: #888; | |
| border-radius: 2px; | |
| } | |
| /* Vertical layout styles (for mobile) */ | |
| .container.vertical { | |
| flex-direction: column; | |
| height: 100vh; | |
| /* Ensure full viewport height */ | |
| } | |
| .left-panel.vertical { | |
| height: 50%; | |
| width: 100%; | |
| max-width: 100%; | |
| max-height: calc(100% - 108px); | |
| } | |
| .right-panel.vertical { | |
| height: 50%; | |
| width: 100%; | |
| max-width: 100%; | |
| max-height: calc(100% - 108px); | |
| } | |
| .divider.vertical { | |
| height: 8px; | |
| width: 100%; | |
| cursor: row-resize; | |
| } | |
| .divider-handle.vertical { | |
| height: 4px; | |
| width: 30px; | |
| } | |
| /* Media query for mobile devices */ | |
| @media (max-width: 768px) { | |
| .container { | |
| flex-direction: column; | |
| height: 100vh; | |
| /* Ensure full viewport height */ | |
| } | |
| .left-panel { | |
| height: 50%; | |
| width: 100%; | |
| max-width: 100%; | |
| max-height: calc(100% - 108px); | |
| } | |
| .right-panel { | |
| height: 50%; | |
| width: 100%; | |
| max-width: 100%; | |
| max-height: calc(100% - 108px); | |
| } | |
| .divider { | |
| height: 8px; | |
| width: 100%; | |
| cursor: row-resize; | |
| } | |
| .divider-handle { | |
| height: 4px; | |
| width: 30px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="panel left-panel"> | |
| <h2>Left/Top Panel</h2> | |
| <p>This is the content for the first panel. You can resize by dragging the divider.</p> | |
| <p>The layout automatically switches to vertical on mobile devices and horizontal on desktop.</p> | |
| <button onclick="toggleLayout()">Toggle Layout</button> | |
| </div> | |
| <div class="divider"> | |
| <div class="divider-handle"></div> | |
| </div> | |
| <div class="panel right-panel"> | |
| <h2>Right/Bottom Panel</h2> | |
| <p>This is the content for the second panel. You can resize by dragging the divider.</p> | |
| <p>The panels automatically adjust to the screen size and orientation.</p> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function () { | |
| const container = document.querySelector('.container'); | |
| const divider = document.querySelector('.divider'); | |
| const leftPanel = document.querySelector('.left-panel'); | |
| const rightPanel = document.querySelector('.right-panel'); | |
| const dividerHandle = document.querySelector('.divider-handle'); | |
| let isDragging = false; | |
| let initialPos = 0; | |
| let initialLeftSize = 0; | |
| let initialRightSize = 0; | |
| let containerSize = 0; | |
| let currentLayout = window.innerWidth <= 768 ? 'vertical' : 'horizontal'; | |
| // Function to check if the layout should be vertical based on screen size | |
| function shouldBeVerticalLayout() { | |
| return window.innerWidth <= 768; | |
| } | |
| // Function to check if the layout is currently vertical | |
| function isVerticalLayout() { | |
| return container.classList.contains('vertical'); | |
| } | |
| // Function to update layout classes | |
| function updateLayoutClasses(forceLayout = null) { | |
| // Determine if layout should change | |
| const shouldBeVertical = forceLayout !== null ? forceLayout === 'vertical' : shouldBeVerticalLayout(); | |
| // Only change if needed | |
| if (shouldBeVertical !== isVerticalLayout()) { | |
| if (shouldBeVertical) { | |
| // Switch to vertical layout | |
| container.classList.add('vertical'); | |
| leftPanel.classList.add('vertical'); | |
| rightPanel.classList.add('vertical'); | |
| divider.classList.add('vertical'); | |
| dividerHandle.classList.add('vertical'); | |
| // Reset panel sizes for vertical layout | |
| leftPanel.style.width = '100%'; | |
| leftPanel.style.height = '50%'; | |
| rightPanel.style.width = '100%'; | |
| rightPanel.style.height = '50%'; | |
| currentLayout = 'vertical'; | |
| } else { | |
| // Switch to horizontal layout | |
| container.classList.remove('vertical'); | |
| leftPanel.classList.remove('vertical'); | |
| rightPanel.classList.remove('vertical'); | |
| divider.classList.remove('vertical'); | |
| dividerHandle.classList.remove('vertical'); | |
| // Reset panel sizes for horizontal layout | |
| leftPanel.style.height = '100%'; | |
| leftPanel.style.width = '50%'; | |
| rightPanel.style.height = '100%'; | |
| rightPanel.style.width = '50%'; | |
| currentLayout = 'horizontal'; | |
| } | |
| } | |
| } | |
| // Initialize layout on load | |
| updateLayoutClasses(); | |
| // Set initial container size | |
| function updateContainerSize() { | |
| if (isVerticalLayout()) { | |
| containerSize = container.offsetHeight; | |
| } else { | |
| containerSize = container.offsetWidth; | |
| } | |
| } | |
| updateContainerSize(); | |
| // Add event listeners for mouse and touch events | |
| divider.addEventListener('mousedown', initDrag); | |
| divider.addEventListener('touchstart', initDrag); | |
| function initDrag(e) { | |
| isDragging = true; | |
| divider.classList.add('active'); | |
| updateContainerSize(); | |
| const vertical = isVerticalLayout(); | |
| if (vertical) { | |
| // For vertical layout | |
| initialLeftSize = leftPanel.offsetHeight; | |
| initialRightSize = rightPanel.offsetHeight; | |
| if (e.type === 'mousedown') { | |
| initialPos = e.clientY; | |
| } else if (e.type === 'touchstart') { | |
| initialPos = e.touches[0].clientY; | |
| } | |
| } else { | |
| // For horizontal layout | |
| initialLeftSize = leftPanel.offsetWidth; | |
| initialRightSize = rightPanel.offsetWidth; | |
| if (e.type === 'mousedown') { | |
| initialPos = e.clientX; | |
| } else if (e.type === 'touchstart') { | |
| initialPos = e.touches[0].clientX; | |
| } | |
| } | |
| // Add event listeners for dragging | |
| document.addEventListener('mousemove', doDrag); | |
| document.addEventListener('touchmove', doDrag); | |
| document.addEventListener('mouseup', stopDrag); | |
| document.addEventListener('touchend', stopDrag); | |
| document.addEventListener('touchcancel', stopDrag); | |
| // Prevent text selection during drag | |
| e.preventDefault(); | |
| } | |
| function doDrag(e) { | |
| if (!isDragging) return; | |
| let currentPos = 0; | |
| const vertical = isVerticalLayout(); | |
| const minSize = 100; // Minimum panel size | |
| const dividerSize = vertical ? divider.offsetHeight : divider.offsetWidth; | |
| if (vertical) { | |
| // For vertical layout | |
| if (e.type === 'mousemove') { | |
| currentPos = e.clientY; | |
| } else if (e.type === 'touchmove') { | |
| currentPos = e.touches[0].clientY; | |
| } | |
| const offset = currentPos - initialPos; | |
| // Calculate new heights | |
| let newTopHeight = initialLeftSize + offset; | |
| let newBottomHeight = initialRightSize - offset; | |
| // Apply constraints | |
| if (newTopHeight < minSize) { | |
| newTopHeight = minSize; | |
| newBottomHeight = containerSize - minSize - dividerSize; | |
| } else if (newBottomHeight < minSize) { | |
| newBottomHeight = minSize; | |
| newTopHeight = containerSize - minSize - dividerSize; | |
| } | |
| // Update panel heights | |
| const topHeightPercent = (newTopHeight / containerSize) * 100; | |
| const bottomHeightPercent = (newBottomHeight / containerSize) * 100; | |
| leftPanel.style.height = `${topHeightPercent}%`; | |
| rightPanel.style.height = `${bottomHeightPercent}%`; | |
| } else { | |
| // For horizontal layout | |
| if (e.type === 'mousemove') { | |
| currentPos = e.clientX; | |
| } else if (e.type === 'touchmove') { | |
| currentPos = e.touches[0].clientX; | |
| } | |
| const offset = currentPos - initialPos; | |
| // Calculate new widths | |
| let newLeftWidth = initialLeftSize + offset; | |
| let newRightWidth = initialRightSize - offset; | |
| // Apply constraints | |
| if (newLeftWidth < minSize) { | |
| newLeftWidth = minSize; | |
| newRightWidth = containerSize - minSize - dividerSize; | |
| } else if (newRightWidth < minSize) { | |
| newRightWidth = minSize; | |
| newLeftWidth = containerSize - minSize - dividerSize; | |
| } | |
| // Update panel widths | |
| const leftWidthPercent = (newLeftWidth / containerSize) * 100; | |
| const rightWidthPercent = (newRightWidth / containerSize) * 100; | |
| leftPanel.style.width = `${leftWidthPercent}%`; | |
| rightPanel.style.width = `${rightWidthPercent}%`; | |
| } | |
| e.preventDefault(); | |
| } | |
| function stopDrag() { | |
| isDragging = false; | |
| divider.classList.remove('active'); | |
| // Remove event listeners | |
| document.removeEventListener('mousemove', doDrag); | |
| document.removeEventListener('touchmove', doDrag); | |
| document.removeEventListener('mouseup', stopDrag); | |
| document.removeEventListener('touchend', stopDrag); | |
| document.removeEventListener('touchcancel', stopDrag); | |
| } | |
| // Prevent default drag behavior | |
| divider.addEventListener('dragstart', function (e) { | |
| e.preventDefault(); | |
| }); | |
| // Handle window resize - improved to actually change layout | |
| let resizeTimeout; | |
| window.addEventListener('resize', function () { | |
| // Debounce resize event | |
| clearTimeout(resizeTimeout); | |
| resizeTimeout = setTimeout(function () { | |
| const shouldBeVertical = shouldBeVerticalLayout(); | |
| const isCurrentlyVertical = isVerticalLayout(); | |
| // Only update if layout should change | |
| if (shouldBeVertical !== isCurrentlyVertical) { | |
| // Force layout update | |
| updateLayoutClasses(); | |
| } | |
| // Always update container size | |
| updateContainerSize(); | |
| }, 250); | |
| }); | |
| // Global function to toggle layout manually | |
| window.toggleLayout = function () { | |
| const currentVertical = isVerticalLayout(); | |
| updateLayoutClasses(currentVertical ? 'horizontal' : 'vertical'); | |
| updateContainerSize(); | |
| }; | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment