Last active
          August 16, 2018 17:29 
        
      - 
      
- 
        Save bwindels/3fbdd9a4675e6aa53099bbc662587245 to your computer and use it in GitHub Desktop. 
    html splitter with flexbox and injectable distribution algorithm
  
        
  
    
      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> | |
| <head> | |
| <meta charset="utf-8"> | |
| <style type="text/css"> | |
| body { | |
| padding: 0; | |
| margin: 0; | |
| } | |
| #container { | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .item { | |
| flex: 1 1 auto; | |
| background: red; | |
| } | |
| .item2 { | |
| flex: 2 1 auto; | |
| background: orange; | |
| } | |
| .resizing .item { | |
| background: blue; | |
| } | |
| .resize-handle { | |
| cursor: row-resize; | |
| flex: 0 0 auto; | |
| background: green; | |
| height: 1px; | |
| padding: 2px | |
| } | |
| .resize-handle.vertical { | |
| cursor: row-resize; | |
| } | |
| .resize-handle.horizontal { | |
| cursor: col-resize; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="container"> | |
| <div class="item"></div> | |
| <div class="resize-handle vertical"></div> | |
| <div class="item2"></div> | |
| <div class="resize-handle vertical"></div> | |
| <div class="item"></div> | |
| <div class="resize-handle vertical"></div> | |
| <div class="item"></div> | |
| <!-- | |
| <div class="resize-handle vertical"></div> | |
| <div class="item"></div> | |
| --> | |
| </div> | |
| <script type="text/javascript"> | |
| class Sizer { | |
| constructor(container, vertical) { | |
| this.container = container; | |
| this.vertical = vertical; | |
| this.containerRect = container.getBoundingClientRect(); | |
| } | |
| getItemPercentage(item) { | |
| const flexGrow = window.getComputedStyle(item).flexGrow; | |
| if (flexGrow === "") { | |
| return null; | |
| } | |
| return parseInt(flexGrow) / 1000; | |
| } | |
| setItemPercentage(item, percent) { | |
| item.style.flexGrow = Math.round(percent * 1000); | |
| } | |
| getItemOffset(item) { | |
| const rect = item.getBoundingClientRect(); | |
| const offset = this.vertical ? rect.y : rect.x; | |
| return offset - this.getOffset(); | |
| } | |
| getItemSize(item) { | |
| return parseInt(window.getComputedStyle(item).width); | |
| } | |
| getTotalSize() { | |
| return this.vertical ? this.containerRect.height : this.containerRect.width; | |
| } | |
| getOffset() { | |
| return this.vertical ? this.containerRect.y : this.containerRect.x; | |
| } | |
| setItemSize(item, size) { | |
| item.style.flexGrow = 0; | |
| item.style.flexShrink = 0; | |
| item.style.flexBasis = `${Math.round(size)}px`; | |
| } | |
| localSizeFromEvent(event) { | |
| const pos = this.vertical ? event.pageY : event.pageX; | |
| return pos - this.getOffset(); | |
| } | |
| } | |
| class FixedDistributor { | |
| constructor(container, items, handleIndex, sizer) { | |
| this.beforeItem = items[handleIndex - 1]; | |
| //this.afterItem = items[handleIndex]; | |
| //this.beforeSize = sizer.getItemSize(beforeItem); | |
| //this.afterSize = sizer.getItemSize(afterSize); | |
| this.beforeOffset = sizer.getItemOffset(this.beforeItem); | |
| this.sizer = sizer; | |
| } | |
| resize(offset) { | |
| const itemSize = offset - this.beforeOffset; | |
| this.sizer.setItemSize(this.beforeItem, itemSize); | |
| } | |
| } | |
| class PercentageDistributor { | |
| constructor(container, items, handleIndex, sizer) { | |
| this.container = container; | |
| this.totalSize = sizer.getTotalSize(); | |
| this.sizer = sizer; | |
| this.beforeItems = items.slice(0, handleIndex); | |
| this.afterItems = items.slice(handleIndex); | |
| const percentages = PercentageDistributor._getPercentages(sizer, items); | |
| this.beforePercentages = percentages.slice(0, handleIndex); | |
| this.afterPercentages = percentages.slice(handleIndex); | |
| } | |
| resize(offset) { | |
| const percent = offset / this.totalSize; | |
| const beforeSum = | |
| this.beforePercentages.reduce((total, p) => total + p, 0); | |
| const beforePercentages = | |
| this.beforePercentages.map(p => (p / beforeSum) * percent); | |
| const afterSum = | |
| this.afterPercentages.reduce((total, p) => total + p, 0); | |
| const afterPercentages = | |
| this.afterPercentages.map(p => (p / afterSum) * (1 - percent)); | |
| this.beforeItems.forEach((item, index) => { | |
| this.sizer.setItemPercentage(item, beforePercentages[index]); | |
| }); | |
| this.afterItems.forEach((item, index) => { | |
| this.sizer.setItemPercentage(item, afterPercentages[index]); | |
| }); | |
| } | |
| static _getPercentages(sizer, items) { | |
| const percentages = items.map(i => sizer.getItemPercentage(i)); | |
| const setPercentages = percentages.filter(p => p !== null); | |
| const unsetCount = percentages.length - setPercentages.length; | |
| const setTotal = setPercentages.reduce((total, p) => total + p, 0); | |
| const implicitPercentage = (1 - setTotal) / unsetCount; | |
| return percentages.map(p => p === null ? implicitPercentage : p); | |
| } | |
| static setPercentage(el, percent) { | |
| el.style.flexGrow = Math.round(percent * 1000); | |
| } | |
| } | |
| const RESIZE_HANDLE_CLASS = "resize-handle"; | |
| function makeResizeable(container, distributorCtor) { | |
| container.addEventListener("mousedown", (event) => { | |
| if (!event.target.classList.contains(RESIZE_HANDLE_CLASS)) { | |
| return; | |
| } | |
| // prevent starting a drag operation | |
| event.preventDefault(); | |
| container.classList.add("resizing"); | |
| const resizeHandle = event.target; | |
| const vertical = resizeHandle.classList.contains("vertical"); | |
| const sizer = new Sizer(container, vertical); | |
| const items = Array.prototype.slice.apply( | |
| container.querySelectorAll(`:not(.${RESIZE_HANDLE_CLASS})`)); | |
| const prevItem = resizeHandle.previousElementSibling; | |
| const handleIndex = items.indexOf(prevItem) + 1; | |
| const distributor = new distributorCtor(container, items, handleIndex, sizer); | |
| const onMouseMove = (event) => { | |
| const offset = sizer.localSizeFromEvent(event); | |
| distributor.resize(offset); | |
| }; | |
| const body = document.body; | |
| const onMouseUp = (event) => { | |
| container.classList.remove("resizing"); | |
| body.removeEventListener("mouseup", onMouseUp, false); | |
| body.removeEventListener("mousemove", onMouseMove, false); | |
| }; | |
| body.addEventListener("mouseup", onMouseUp, false); | |
| body.addEventListener("mousemove", onMouseMove, false); | |
| }, false); | |
| } | |
| makeResizeable(document.getElementById("container"), PercentageDistributor); | |
| </script> | |
| </body> | |
| </html> | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment