├── README.md ├── iconfont ├── iconfont.eot ├── iconfont.ttf ├── iconfont.woff ├── iconfont.woff2 └── iconfont.svg ├── index.html ├── styles ├── style.css └── tab.css └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # tab-oop 2 | -------------------------------------------------------------------------------- /iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianchengLee/tab-oop/master/iconfont/iconfont.eot -------------------------------------------------------------------------------- /iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianchengLee/tab-oop/master/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianchengLee/tab-oop/master/iconfont/iconfont.woff -------------------------------------------------------------------------------- /iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianchengLee/tab-oop/master/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 面向对象 Tab 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

17 | Js 面向对象 动态添加标签页 18 |

19 |
20 | 21 | 40 | 41 | 42 |
43 |
测试1
44 |
测试2
45 |
测试3
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /styles/style.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('./iconfont/iconfont.eot?t=1553960438096'); /* IE9 */ 3 | src: url('./iconfont/iconfont.eot?t=1553960438096#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAK4AAsAAAAABmwAAAJrAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCcAp4fwE2AiQDCAsGAAQgBYRtBzAbpQXIrrApw71oi3CCOyzEy8RvE4yIN8TD036/zp03qCYRjaJZNBFFS/gREoRGipQKofjuNrb+9XbTqrmXcqWzfTRDqFqWkhAJzYToaE6LQ7Q30CirRqSKMnj58DdIdrNAdhoTQJa5VGfLrtiAy+lPoAcZdUC57UljTR4TMAo4oL0xiqwYG8YueIHPCdTqYajty/t+bUpmrwvEnUK42lQhLMssVy1UNhzN4kmF6vSQVvMY/T5+HEU1SUXBbti7uBBrx++cgqJULp0GhAgBna5AgSkgE0eN6R1NwTitNt0yAI5VG7wr/8AljmoX7K+zq+tBF1Q8k9JTPWp1AjnJDgCzmM3bU0V31dsvV3M2eC6fHjaGfX/qS7U5Gr58vj6uD0bgxudyrV/OtHHyP+NZnpO1txbktjdY+3FB61+7nxeOzq8niGYnRwT3v3aZxeXf6rrNxl5//49WlEtZUUL1Pj3Bv1EO7MuG2namrCkbvcnApLUJtWpRhv2tzlRLx43kQ7WO2/FW6c5QqDZEZnYKFeosoVK1NdSa5E/XaVM1Ra7BhAEQmk0kjV5QaLbIzG5U6HRRqTkK1DqJtivrjMT1zJaNnIsihAiyQE3JdbszcW0Xiadzdl4d8UO0HSUGNDNXzl2hifYSO5pPjrorgdjUAAavoa5TKDZVUXD3kuuOOzh70fShvUiN2owtNsRxIREIIiATUCYpGO2aqXy/CxEeHcfuaKrLDiGbQ5kcEMsNIK8M5qCmR3mn8RFHOpcECBtlAAwWIZ2OAqV5kQoJXHvShORYBzrDZKhhb3uT8QPlrA3bmsKZV6i89DiTV2o1AAAA') format('woff2'), 5 | url('./iconfont/iconfont.woff?t=1553960438096') format('woff'), 6 | url('./iconfont/iconfont.ttf?t=1553960438096') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('./iconfont/iconfont.svg?t=1553960438096#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-guanbi:before { 19 | content: "\e676"; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /styles/tab.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | ul li { 7 | list-style: none; 8 | } 9 | 10 | main { 11 | width: 960px; 12 | height: 500px; 13 | border-radius: 10px; 14 | margin: 50px auto; 15 | } 16 | 17 | main h4 { 18 | height: 100px; 19 | line-height: 100px; 20 | text-align: center; 21 | } 22 | 23 | .tabsbox { 24 | width: 900px; 25 | margin: 0 auto; 26 | height: 400px; 27 | border: 1px solid lightsalmon; 28 | position: relative; 29 | } 30 | 31 | nav ul { 32 | overflow: hidden; 33 | } 34 | 35 | nav ul li { 36 | float: left; 37 | width: 100px; 38 | height: 50px; 39 | line-height: 50px; 40 | text-align: center; 41 | border-right: 1px solid #ccc; 42 | position: relative; 43 | } 44 | 45 | nav ul li.liactive { 46 | border-bottom: 2px solid #fff; 47 | z-index: 9; 48 | } 49 | 50 | #tab input { 51 | width: 80%; 52 | height: 60%; 53 | } 54 | 55 | nav ul li span:last-child { 56 | position: absolute; 57 | user-select: none; 58 | font-size: 12px; 59 | top: -18px; 60 | right: 0; 61 | display: inline-block; 62 | height: 20px; 63 | } 64 | 65 | .tabadd { 66 | position: absolute; 67 | /* width: 100px; */ 68 | top: 0; 69 | right: 0; 70 | } 71 | 72 | .tabadd span { 73 | display: block; 74 | width: 20px; 75 | height: 20px; 76 | line-height: 20px; 77 | text-align: center; 78 | border: 1px solid #ccc; 79 | float: right; 80 | margin: 10px; 81 | user-select: none; 82 | } 83 | 84 | .tabscon { 85 | width: 100%; 86 | height: 300px; 87 | position: absolute; 88 | padding: 30px; 89 | top: 50px; 90 | left: 0px; 91 | box-sizing: border-box; 92 | border-top: 1px solid #ccc; 93 | } 94 | 95 | .tabscon section, 96 | .tabscon section.conactive { 97 | display: none; 98 | width: 100%; 99 | height: 100%; 100 | } 101 | 102 | .tabscon section.conactive { 103 | display: block; 104 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | class Tab { 2 | 3 | // 抽取对象 共有的方法 对象就是 tab 4 | // tab 功能 有 点击切换 点击 + 添加 tab 点击 减去 删除tab 5 | constructor(id) { 6 | // 最大的盒子 7 | this.id = document.querySelector(id); 8 | this.firstNav = this.id.querySelector('.fisrstnav'); 9 | this.tabsCon = this.id.querySelector('.tabscon'); 10 | // 里面的加号 11 | this.add = this.id.querySelector('.tabadd'); 12 | // 获取静态列表 13 | this.updateNodeList(); 14 | // 注册事件 15 | this.registerAction(); 16 | } 17 | 18 | // 重新获取最新的列表 19 | updateNodeList() { 20 | // 上面 li 21 | this.lis = this.id.querySelectorAll('li'); 22 | // 下面的切换seciton 23 | this.sections = this.id.querySelectorAll('section'); 24 | // 减号 25 | this.jian = this.id.querySelectorAll('.iconfont'); 26 | // 双击修改 span 27 | this.titleSpan = this.id.querySelectorAll('.fisrstnav li span:first-child'); 28 | } 29 | 30 | registerAction() { 31 | // 添加Tab不需要事件代理 32 | this.add.onclick = this.addTab.bind(this.add, this) 33 | // 事件代理 34 | this.delegate(this.firstNav, 'li', 'click', this.tabToggle) 35 | this.delegate(this.firstNav, '.icon-guanbi', 'click', this.removeTab) 36 | this.delegate(this.firstNav, '.title', 'dblclick', this.change) 37 | this.delegate(this.tabsCon, '.content', 'dblclick', this.change) 38 | } 39 | 40 | // 添加tab 41 | addTab(tab) { 42 | tab.clearClass(); 43 | var random = Math.random() 44 | var li = '
  • Tab
  • ' 45 | var section = '
    New Tab content' + random + '
    ' 46 | 47 | // 将元素追加到页面结构中去 48 | tab.id.querySelector('.fisrstnav ul:first-child').insertAdjacentHTML('beforeend', li) 49 | tab.id.querySelector('.tabscon').insertAdjacentHTML('beforeend', section) 50 | tab.updateNodeList(); 51 | } 52 | 53 | // 切换tab栏 54 | tabToggle(tab) { 55 | tab.clearClass(); 56 | this.classList.add('liactive'); 57 | var i = Array.prototype.indexOf.call(tab.lis, this) 58 | tab.sections[i].classList.add('conactive'); 59 | } 60 | 61 | // 清除原先样式 62 | clearClass() { 63 | for (var i = 0; i < this.lis.length; i++) { 64 | this.lis[i].classList.remove('liactive'); 65 | this.sections[i].classList.remove('conactive') 66 | } 67 | } 68 | 69 | // 移除 tab 做法 70 | removeTab(tab) { 71 | var i = Array.prototype.indexOf.call(tab.lis, this.parentNode) 72 | 73 | // console.log(i, tab.lis) 74 | tab.lis[i].remove() 75 | tab.sections[i].remove() 76 | tab.updateNodeList() 77 | 78 | if (--i < 0) i = 0 79 | 80 | if (tab.lis[i]) { 81 | tab.lis[i].click() 82 | } 83 | } 84 | 85 | // 更改文字内容 86 | change() { 87 | window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); 88 | var str = this.innerText; 89 | this.innerHTML = ''; 90 | var input = this.children[0]; 91 | input.value = str; 92 | input.focus(); 93 | input.onblur = function () { 94 | this.parentNode.innerHTML = this.value; 95 | } 96 | input.onkeydown = function (e) { 97 | if (e.keyCode === 13) this.blur() 98 | } 99 | } 100 | 101 | // 核心: 事件代理 + 闭包 有一定难度 102 | delegate(parent, selector, eventName, eventHandler) { 103 | function getTarget(ele) { 104 | if (ele.matches(selector)) return ele 105 | while (ele = ele.parentNode) { 106 | if (ele.matches(selector)) { 107 | return ele 108 | } 109 | } 110 | return null 111 | } 112 | parent.addEventListener(eventName, function (e) { 113 | if (e.target.matches(selector) || e.target.matches(selector + ' *')) { 114 | eventHandler.call(getTarget(e.target), this, e); 115 | this.updateNodeList() 116 | } 117 | }.bind(this)) 118 | } 119 | 120 | } 121 | var tab = new Tab('#tab'); --------------------------------------------------------------------------------