.container
.view(
v-for='s in strings'
v-responsive-text='s'
responsive-text-offset='24'
)
//- .view(
//- v-for='s in strings'
//- v-responsive-text='s'
//- responsive-text-offset='12'
//- responsive-text-max='64'
//- )
View Compiled
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
.container {
width: 50%;
max-width: 720px;
margin: 0 auto;
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: baseline;
font-family: 'Open Sans';
}
.view {
width: 275px;
font-size: 22px;
margin: 4px;
overflow: hidden;
white-space: nowrap;
text-align: center;
// debug...
// border: 1px solid rgba(blue, 0.1);
}
View Compiled
Vue.directive('responsive-text', {
params: [
'responsive-text-min',
'responsive-text-max',
'responsive-text-offset'
],
bind() {
this.__getFontSize = (el) => {
const fontSize = window.getComputedStyle(el, null)
.getPropertyValue('font-size')
return parseFloat(fontSize.replace(/[^0-9.]/g, ''))
}
this.__addPx = (element, n) => {
const x = this.__getFontSize(element) + n
return `${x}px`
}
this.__getRandomId = () => {
return `responsive-text__${Math.random().toString(36).slice(2, 15)}`
}
},
update(value) {
if (!value) return
const id = this.__getRandomId()
const offset = this.params.responsiveTextOffset || 0
const max = this.params.responsiveTextMax
const min = this.params.responsiveTextMin || 4
// create a virtual offscreen clone of this.el with styles
// that will enable us to calculate the actual text width
const dummyEl = document.createElement(this.el.tagName)
dummyEl.className = this.el.className
dummyEl.setAttribute('id', id)
dummyEl.textContent = value
const dummyStyles = {
position: 'absolute',
top: '-9999px',
height: '0 !important',
opacity: 0
}
Object.keys(dummyStyles).forEach(key => {
dummyEl.style[key] = dummyStyles[key]
})
// css priorities cannot be set directly
dummyEl.style.setProperty('width', 'auto', 'important')
// adjacent for easier debugging
this.el.parentNode.insertBefore(dummyEl, this.el)
Vue.nextTick(() => {
// safe guard just in case
let maxIterations = 200
if (dummyEl.scrollWidth === this.el.scrollWidth) {
this.el.textContent = value
this.el.parentNode.removeChild(dummyEl)
return
} else if (dummyEl.scrollWidth < this.el.scrollWidth - offset) {
// enlarge
while(--maxIterations &&
dummyEl.scrollWidth < this.el.scrollWidth - offset) {
if (max && this.__getFontSize(dummyEl) >= max) {
dummyEl.style.setProperty('font-size', max + 'px')
break
}
dummyEl.style.setProperty('font-size', this.__addPx(dummyEl, 1))
}
} else {
// shrink
while (--maxIterations &&
dummyEl.scrollWidth > this.el.scrollWidth - offset) {
if (min && this.__getFontSize(dummyEl) <= min) {
dummyEl.style.setProperty('font-size', min + 'px')
break
}
dummyEl.style.setProperty('font-size', this.__addPx(dummyEl, -1))
}
}
this.el.textContent = value
this.el.style.setProperty('font-size', this.__getFontSize(dummyEl) + 'px')
this.el.parentNode.removeChild(dummyEl)
})
}
})
// +-----------+
// | DEMO CODE |
// +-----------+
new Vue({
el: '.container',
data: {
strings: [
'abc',
'1 2 3',
'foobarbaz',
'foo bar baz',
'a beautiful day for a neighbor',
'gargantua',
'Marry Poppins',
'I Care Because You Do'
],
test: 'foo'
}
})
View Compiled
This Pen doesn't use any external CSS resources.