新增标签栏左右移动按钮

This commit is contained in:
黄凯伦 2018-10-10 11:08:38 +08:00
parent ed4427243d
commit c25869e287
3 changed files with 379 additions and 83 deletions

View File

@ -1,37 +1,86 @@
<template> <template>
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll"> <div ref="scrollContainer" class="scroll-container" @wheel.prevent="handleScroll">
<slot/> <div ref="scrollWrapper" :style="{left: left + 'px'}" class="scroll-wrapper">
</el-scrollbar> <slot />
</div>
</div>
</template> </template>
<script> <script>
import createDetectElementResize from '@/vendor/detectElementResize'
const padding = 15 // tag's padding const padding = 15 // tag's padding
const scrollPanelLeft = 30
export default { export default {
name: 'ScrollPane', name: 'ScrollPane',
data() { data() {
return { return {
left: 0 left: scrollPanelLeft
} }
}, },
mounted() {
this.detectElementResize = createDetectElementResize()
this.detectElementResize.addResizeListener(this.$refs.scrollContainer, () => {
const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth
const $wrapper = this.$refs.scrollWrapper
const $wrapperWidth = $wrapper.offsetWidth
if ($containerWidth - padding > $wrapperWidth) {
this.left = scrollPanelLeft
}
})
},
methods: { methods: {
scrollLeft() {
this.left = Math.min(scrollPanelLeft, this.left + 300)
},
scrollRight() {
const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth
const $wrapper = this.$refs.scrollWrapper
const $wrapperWidth = $wrapper.offsetWidth
if ($containerWidth - padding < $wrapperWidth) {
this.left = Math.max(this.left - 300, $containerWidth - $wrapperWidth - padding - scrollPanelLeft)
}
},
handleScroll(e) { handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40 const eventDelta = e.wheelDelta || -e.deltaY * 3
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap const $container = this.$refs.scrollContainer
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4 const $containerWidth = $container.offsetWidth
const $wrapper = this.$refs.scrollWrapper
const $wrapperWidth = $wrapper.offsetWidth
if (eventDelta > 0) {
this.left = Math.min(scrollPanelLeft, this.left + eventDelta)
} else {
if ($containerWidth - padding < $wrapperWidth) {
if (this.left < -($wrapperWidth - $containerWidth + padding)) {
this.left = this.left
} else {
this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding - scrollPanelLeft)
}
} else {
this.left = scrollPanelLeft
}
}
}, },
moveToTarget($target) { moveToTarget($target) {
const $container = this.$refs.scrollContainer.$el const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth const $containerWidth = $container.offsetWidth
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
const $targetLeft = $target.offsetLeft const $targetLeft = $target.offsetLeft
const $targetWidth = $target.offsetWidth const $targetWidth = $target.offsetWidth
if ($targetLeft > $containerWidth) {
// tag in the right if ($targetLeft < -this.left) {
$scrollWrapper.scrollLeft = $targetLeft - $containerWidth + $targetWidth + padding
} else {
// tag in the left // tag in the left
$scrollWrapper.scrollLeft = $targetLeft - padding this.left = -$targetLeft + padding
} else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {
// tag in the current view
// eslint-disable-line
} else {
// tag in the right
this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)
} }
} }
} }
@ -39,18 +88,13 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
.scroll-container { .scroll-container {
white-space: nowrap; white-space: nowrap;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
/deep/ { .scroll-wrapper {
.el-scrollbar__bar { position: absolute;
bottom: 0px;
}
.el-scrollbar__wrap {
height: 49px;
} }
} }
}
</style> </style>

226
src/vendor/detectElementResize.js vendored Normal file
View File

@ -0,0 +1,226 @@
/* eslint-disable */
export default function createDetectElementResize(nonce) {
// Check `document` and `window` in case of server-side rendering
var _window
if (typeof window !== 'undefined') {
_window = window
} else if (typeof self !== 'undefined') {
_window = self
} else {
_window = global
}
var attachEvent = typeof document !== 'undefined' && document.attachEvent
if (!attachEvent) {
var requestFrame = (function() {
var raf =
_window.requestAnimationFrame ||
_window.mozRequestAnimationFrame ||
_window.webkitRequestAnimationFrame ||
function(fn) {
return _window.setTimeout(fn, 20)
}
return function(fn) {
return raf(fn)
}
})()
var cancelFrame = (function() {
var cancel =
_window.cancelAnimationFrame ||
_window.mozCancelAnimationFrame ||
_window.webkitCancelAnimationFrame ||
_window.clearTimeout
return function(id) {
return cancel(id)
}
})()
var resetTriggers = function(element) {
var triggers = element.__resizeTriggers__,
expand = triggers.firstElementChild,
contract = triggers.lastElementChild,
expandChild = expand.firstElementChild
contract.scrollLeft = contract.scrollWidth
contract.scrollTop = contract.scrollHeight
expandChild.style.width = expand.offsetWidth + 1 + 'px'
expandChild.style.height = expand.offsetHeight + 1 + 'px'
expand.scrollLeft = expand.scrollWidth
expand.scrollTop = expand.scrollHeight
}
var checkTriggers = function(element) {
return (
element.offsetWidth != element.__resizeLast__.width ||
element.offsetHeight != element.__resizeLast__.height
)
}
var scrollListener = function(e) {
// Don't measure (which forces) reflow for scrolls that happen inside of children!
if (
e.target.className.indexOf('contract-trigger') < 0 &&
e.target.className.indexOf('expand-trigger') < 0
) {
return
}
var element = this
resetTriggers(this)
if (this.__resizeRAF__) {
cancelFrame(this.__resizeRAF__)
}
this.__resizeRAF__ = requestFrame(function() {
if (checkTriggers(element)) {
element.__resizeLast__.width = element.offsetWidth
element.__resizeLast__.height = element.offsetHeight
element.__resizeListeners__.forEach(function(fn) {
fn.call(element, e)
})
}
})
}
/* Detect CSS Animations support to detect element display/re-attach */
var animation = false,
keyframeprefix = '',
animationstartevent = 'animationstart',
domPrefixes = 'Webkit Moz O ms'.split(' '),
startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(
' ',
),
pfx = ''
{
var elm = document.createElement('fakeelement')
if (elm.style.animationName !== undefined) {
animation = true
}
if (animation === false) {
for (var i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
pfx = domPrefixes[i]
keyframeprefix = '-' + pfx.toLowerCase() + '-'
animationstartevent = startEvents[i]
animation = true
break
}
}
}
}
var animationName = 'resizeanim'
var animationKeyframes =
'@' +
keyframeprefix +
'keyframes ' +
animationName +
' { from { opacity: 0; } to { opacity: 0; } } '
var animationStyle =
keyframeprefix + 'animation: 1ms ' + animationName + '; '
}
var createStyles = function(doc) {
if (!doc.getElementById('detectElementResize')) {
// opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
var css =
(animationKeyframes || '') +
'.resize-triggers { ' +
(animationStyle || '') +
'visibility: hidden; opacity: 0; } ' +
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: " "; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; z-index: -1; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }',
head = doc.head || doc.getElementsByTagName('head')[0],
style = doc.createElement('style')
style.id = 'detectElementResize'
style.type = 'text/css'
if (nonce != null) {
style.setAttribute('nonce', nonce)
}
if (style.styleSheet) {
style.styleSheet.cssText = css
} else {
style.appendChild(doc.createTextNode(css))
}
head.appendChild(style)
}
}
var addResizeListener = function(element, fn) {
if (attachEvent) {
element.attachEvent('onresize', fn)
} else {
if (!element.__resizeTriggers__) {
var doc = element.ownerDocument
var elementStyle = _window.getComputedStyle(element)
if (elementStyle && elementStyle.position == 'static') {
element.style.position = 'relative'
}
createStyles(doc)
element.__resizeLast__ = {}
element.__resizeListeners__ = [];
(element.__resizeTriggers__ = doc.createElement('div')).className =
'resize-triggers'
element.__resizeTriggers__.innerHTML =
'<div class="expand-trigger"><div></div></div>' +
'<div class="contract-trigger"></div>'
element.appendChild(element.__resizeTriggers__)
resetTriggers(element)
element.addEventListener('scroll', scrollListener, true)
/* Listen for a css animation to detect element display/re-attach */
if (animationstartevent) {
element.__resizeTriggers__.__animationListener__ = function animationListener(
e,
) {
if (e.animationName == animationName) {
resetTriggers(element)
}
}
element.__resizeTriggers__.addEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__,
)
}
}
element.__resizeListeners__.push(fn)
}
}
var removeResizeListener = function(element, fn) {
if (attachEvent) {
element.detachEvent('onresize', fn)
} else {
element.__resizeListeners__.splice(
element.__resizeListeners__.indexOf(fn),
1,
)
if (!element.__resizeListeners__.length) {
element.removeEventListener('scroll', scrollListener, true)
if (element.__resizeTriggers__.__animationListener__) {
element.__resizeTriggers__.removeEventListener(
animationstartevent,
element.__resizeTriggers__.__animationListener__,
)
element.__resizeTriggers__.__animationListener__ = null
}
try {
element.__resizeTriggers__ = !element.removeChild(
element.__resizeTriggers__,
)
} catch (e) {
// Preact compat; see developit/preact-compat/issues/228
}
}
}
}
return {
addResizeListener,
removeResizeListener
}
}

View File

@ -1,5 +1,8 @@
<template> <template>
<div class="tags-view-container"> <div class="tags-view-container">
<span class="scroll-left-btn" @click="scrollLeft">
<i class="el-icon-caret-left"/>
</span>
<scroll-pane ref="scrollPane" class="tags-view-wrapper"> <scroll-pane ref="scrollPane" class="tags-view-wrapper">
<router-link <router-link
v-for="tag in Array.from(visitedViews)" v-for="tag in Array.from(visitedViews)"
@ -13,6 +16,9 @@
<span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/> <span class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)"/>
</router-link> </router-link>
</scroll-pane> </scroll-pane>
<span class="scroll-right-btn" @click="scrollRight">
<i class="el-icon-caret-right"/>
</span>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu"> <ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li> <li @click="refreshSelectedTag(selectedTag)">{{ $t('tagsView.refresh') }}</li>
<li @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li> <li @click="closeSelectedTag(selectedTag)">{{ $t('tagsView.close') }}</li>
@ -58,6 +64,12 @@ export default {
this.addViewTags() this.addViewTags()
}, },
methods: { methods: {
scrollLeft() {
this.$refs.scrollPane.scrollLeft()
},
scrollRight() {
this.$refs.scrollPane.scrollRight()
},
generateTitle, // generateTitle by vue-i18n generateTitle, // generateTitle by vue-i18n
generateRoute() { generateRoute() {
if (this.$route.name) { if (this.$route.name) {
@ -132,70 +144,84 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
.tags-view-container { .tags-view-container {
height: 34px; position: relative;
width: 100%; .scroll-left-btn, .scroll-right-btn {
background: #fff; position: absolute;
border-bottom: 1px solid #d8dce5; left: 0;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04); top: 0;
.tags-view-wrapper { width: 33px;
.tags-view-item { height: 33px;
display: inline-block; line-height: 33px;
text-align: center;
cursor: pointer;
z-index: 999;
background-color: #fff;
}
.scroll-right-btn {
left: auto;
right: 0;
}
.tags-view-wrapper {
position: relative; position: relative;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff; background: #fff;
padding: 0 8px; height: 34px;
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-item {
display: inline-block;
position: relative;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
&:first-of-type {
margin-left: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px; font-size: 12px;
margin-left: 5px; font-weight: 400;
margin-top: 4px; color: #333;
&:first-of-type { box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
margin-left: 15px; li {
} margin: 0;
&:last-of-type { padding: 7px 16px;
margin-right: 15px; cursor: pointer;
} &:hover {
&.active { background: #eee;
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 2px;
} }
} }
} }
} }
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
</style> </style>
<style rel="stylesheet/scss" lang="scss"> <style rel="stylesheet/scss" lang="scss">