Last active
August 31, 2017 17:56
-
-
Save lstrrs/4d5cfebce25e2d4a1865ebf658314ee6 to your computer and use it in GitHub Desktop.
Line Clamp 2
This file contains 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
import Ember from 'ember'; | |
export default Ember.Component.extend({ | |
componentName: 'LineClamp', | |
//tagName: 'span', | |
text: '', | |
truncatedText: '', | |
lines: 3, | |
init() { | |
this._super(...arguments); | |
this.resize = this.resize.bind(this); | |
this.calcTargetWidth = this.calcTargetWidth.bind(this); | |
this.measureWidth = this.measureWidth.bind(this); | |
this.getLines = this.getLines.bind(this); | |
this.onTruncate = this.onTruncate.bind(this); | |
// Element used for measuring purposes | |
this.ellipsisElement = document.createElement('span'); | |
this.ellipsisElement.style = 'position:fixed;visibility:hidden;top:0;left:0'; | |
this.ellipsisElement.innerHTML = `... <a href='#'>${this.moreText}</a>`; | |
// Actual elment for ellipsis/see more | |
this.ellipsis = `<span>... <a class="line-clamp__more" href='#'>${this.moreText}</a></span>`; | |
this._id = `${this.componentName}${Math.floor((Math.random() * 100) + 1)}`; | |
console.time(`${this._id}-render-${++this.renderCount}`); | |
}, | |
click(e) { | |
e.preventDefault(); | |
const target = e.target; | |
if (target.classList.contains('line-clamp__more')) { | |
const toggleTruncate = this.get('toggleTruncate'); | |
if (typeof toggleTruncate === 'function') { | |
toggleTruncate(e); | |
} | |
} | |
}, | |
renderCount: 0, | |
didRender() { | |
clearTimeout(this.perfTimeout); | |
console.timeEnd(`${this._id}-render-${this.renderCount}`); | |
console.time(`${this._id}-render-${++this.renderCount}`); | |
this.perfTimeout = setTimeout(() => { | |
console.timeEnd(`${this._id}-render-${this.renderCount}`); | |
console.log('-render-done'); | |
}, 5000); | |
}, | |
didInsertElement() { | |
const canvas = document.createElement('canvas'); | |
this.canvasContext = canvas.getContext('2d'); | |
document.body.appendChild(this.ellipsisElement); | |
this.calcTargetWidth(() => { | |
// Node not needed in document tree to read its content | |
//if (this.get('text')) { | |
//console.log('Text: ', this.get('text')); | |
//this.get('text').parentNode.removeChild(this.get('text')); | |
//} | |
}); | |
this.bindResize(); | |
}, | |
willDestroyElement() { | |
this.ellipsisElement.parentNode.removeChild(ellipsisElement); | |
this.unbindResize(); | |
window.cancelAnimationFrame(this.timeout); | |
}, | |
bindResize() { | |
window.addEventListener('resize', this.get('resize')); | |
this._resizeHandlerRegistered = true; | |
}, | |
unbindResize() { | |
if (this._resizeHandlerRegistered) { | |
window.removeEventListener('resize', this.get('resize')); | |
this._resizeHandlerRegistered = false; | |
} | |
}, | |
resize() { | |
this.calcTargetWidth(); | |
}, | |
innerText(node) { | |
const div = document.createElement('div'); | |
const contentKey = 'innerText' in window.HTMLElement.prototype ? 'innerText' : 'textContent'; | |
div.innerHTML = node.innerHTML.replace(/\r\n|\r|\n/g, ' '); | |
let text = div[contentKey]; | |
const test = document.createElement('div'); | |
test.innerHTML = 'foo<br/>bar'; | |
if (test[contentKey].replace(/\r\n|\r/g, '\n') !== 'foo\nbar') { | |
div.innerHTML = div.innerHTML.replace(/<br.*?[\/]?>/gi, '\n'); | |
text = div[contentKey]; | |
} | |
return text; | |
}, | |
onTruncate(didTruncate) { | |
const handleTruncate = this.get('handleTruncate'); | |
if (typeof handleTruncate === 'function') { | |
this.timeout = window.requestAnimationFrame(() => { | |
handleTruncate(didTruncate); | |
}); | |
} | |
}, | |
calcTargetWidth(callback) { | |
const targetWidth = this.element.getBoundingClientRect().width; | |
if (!targetWidth) { | |
return window.requestAnimationFrame(() => this.calcTargetWidth(callback)); | |
} | |
const { | |
fontWeight, | |
fontStyle, | |
fontSize, | |
fontFamily | |
} = window.getComputedStyle(this.element); | |
const font = `${fontWeight} ${fontStyle} ${fontSize} ${fontFamily}`; | |
this.canvasContext.font = font; | |
this.set('targetWidth', targetWidth); | |
}, | |
measureWidth(text) { | |
return this.canvasContext && this.canvasContext.measureText(text).width; | |
}, | |
ellipsisWidth(node) { | |
return node.offsetWidth; | |
}, | |
getLines() { | |
const lines = []; | |
const numLines = this.get('lines'); | |
//const text = this.innerText(this.element); | |
const text = this.text;//this.innerText(this.element); | |
const textLines = text.split('\n').map(line => line.split(' ')); | |
let didTruncate = true; | |
const ellipsisWidth = this.ellipsisWidth(this.ellipsisElement); | |
for (let line = 1; line <= numLines; line++) { | |
const textWords = textLines[0]; | |
// Handle newline | |
if (textWords.length === 0) { | |
lines.push(); | |
textLines.shift(); | |
line--; | |
continue; | |
} | |
let resultLine = textWords.join(' '); | |
if (this.measureWidth(resultLine) <= this.targetWidth) { | |
if (textLines.length === 1) { | |
// Line is end of text and fits without truncating | |
didTruncate = false; | |
lines.push(resultLine); | |
break; | |
} | |
} | |
if (line === numLines) { | |
// Binary search determining the longest possible line inluding truncate string | |
const textRest = textWords.join(' '); | |
let lower = 0; | |
let upper = textRest.length - 1; | |
while (lower <= upper) { | |
const middle = Math.floor((lower + upper) / 2); | |
const testLine = textRest.slice(0, middle + 1); | |
if (this.measureWidth(testLine) + ellipsisWidth <= this.targetWidth) { | |
lower = middle + 1; | |
} else { | |
upper = middle - 1; | |
} | |
} | |
resultLine = Ember.String.htmlSafe(`<span>${textRest.slice(0, lower)}${this.get('ellipsis')}</span>`); | |
} else { | |
// Binary search determining when the line breaks | |
let lower = 0; | |
let upper = textWords.length - 1; | |
while (lower <= upper) { | |
const middle = Math.floor((lower + upper) / 2); | |
const testLine = textWords.slice(0, middle + 1).join(' '); | |
if (this.measureWidth(testLine) <= this.targetWidth) { | |
lower = middle + 1; | |
} else { | |
upper = middle - 1; | |
} | |
} | |
// The first word of this line is too long to fit it | |
if (lower === 0) { | |
// Jump to processing of last line | |
line = numLines - 1; | |
continue; | |
} | |
resultLine = Ember.String.htmlSafe(textWords.slice(0, lower).join(' ')); | |
textLines[0].splice(0, lower); | |
} | |
lines.push(resultLine); | |
} | |
this.onTruncate(didTruncate); | |
return lines; | |
}, | |
renderLine(line, i, arr) { | |
if (i === arr.length - 1) { | |
return `<span key=${i}>${line}</span>`; | |
} else { | |
const br = '<br />'; | |
console.log('Line: ', line); | |
if (line) { | |
return `<span key=${i}>${line}</span>${br}`; | |
} else { | |
return br; | |
} | |
} | |
}, | |
displayText: Ember.computed('lines', 'text', 'targetWidth', function getDisplayText() { | |
const mounted = !!(this.element && this.get('targetWidth')); | |
if (typeof window !== 'undefined' && mounted) { | |
if (this.get('lines') > 0) { | |
//return this.getLines().map(this.renderLine).join(' '); | |
return this.getLines(); | |
} else { | |
this.onTruncate(false); | |
return this.get('text'); | |
} | |
} | |
return this.get('text'); | |
}), | |
actions: { | |
} | |
}); |
This file contains 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
import Ember from 'ember'; | |
export default Ember.Component.extend({ | |
expanded: false, | |
truncated: false, | |
lines: 3, | |
seeLessText: 'See Less', | |
init() { | |
this._super(...arguments); | |
/* | |
this.handleTruncate = this.handleTruncate.bind(this); | |
this.toggleTruncate = this.toggleTruncate.bind(this); | |
*/ | |
this.seeMoreText = 'See More'; | |
}, | |
numLines: Ember.computed('expanded', function getNumLines() { | |
return !this.get('expanded') && this.get('lines'); | |
}), | |
actions: { | |
handleTruncate(truncated) { | |
if (this.get('truncated') !== truncated) { | |
this.set('truncated', truncated); | |
} | |
}, | |
toggleTruncate() { | |
event.preventDefault(); | |
this.toggleProperty('expanded'); | |
}, | |
}, | |
}); |
This file contains 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
import Ember from 'ember'; | |
export default Ember.Controller.extend({ | |
appName: 'Ember Twiddle', | |
init() { | |
this._super(...arguments); | |
this.text = 'After almost 6 years at LinkedIn, I\'m off in search of my next adventure! I am so grateful to everyone who has made LinkedIn such a transformational experience. As I wrote in my goodbye email, I realized how much I have enjoyed my time here at LinkedIn.'; | |
this.text2 = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'; | |
this.truncate = true; | |
}, | |
}); |
This file contains 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
body { | |
margin: 12px 16px; | |
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
font-size: 12pt; | |
} | |
.line-clamp { | |
overflow: hidden; | |
position: relative; | |
} | |
.line-clamp--single-line { | |
white-space: nowrap; | |
text-overflow: ellipsis; | |
} | |
.line-clamp--multi-line { | |
/* autoprefixer: off */ | |
display: -webkit-box; | |
-webkit-box-orient: vertical; | |
/* autoprefixer: on */ | |
text-overflow: ellipsis; | |
} |
This file contains 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
{ | |
"version": "0.12.1", | |
"EmberENV": { | |
"FEATURES": {} | |
}, | |
"options": { | |
"use_pods": false, | |
"enable-testing": false | |
}, | |
"dependencies": { | |
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js", | |
"ember": "2.12.0", | |
"ember-template-compiler": "2.12.0", | |
"ember-testing": "2.12.0" | |
}, | |
"addons": { | |
"ember-data": "2.12.1" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment