Ответ 1
Вот очень быстрое решение этой проблемы, которое использует технику для хранения ширины каждого слова в тексте, а затем строит каждую строку на основе максимальной ширины и накопленной ширины слов в строке. Очень мало манипуляций с DOM так быстро. Даже работает с параметром изменения размера без дросселирования и выглядит великолепно:)
Только одно манипулирование DOM за обновление! Автоматические зажимы при изменении размера! Все, что вам нужно сделать, это предоставить ему 2 свойства. Текстовое свойство текста, который вы хотите зафиксировать, и свойство numi lines, обозначающее, сколько строк вы хотите отобразить. Вы можете установить reset= {false}, если хотите, но я действительно не вижу необходимости. Он очень быстро изменяется.
Надеюсь, вам понравится и вы можете задать любой вопрос, который у вас есть! Ниже приведен код es6, и здесь работает Codepen, который был слегка адаптирован для работы с Codepen.io.
Я рекомендую загрузить код и изменить размер вашего окна, чтобы узнать, как быстро он пересчитывает.
EDIT: я обновил этот компонент, чтобы вы могли добавлять пользовательские функции для расширения и сглаживания. Они полностью необязательны, и вы можете предоставить любую часть объекта управления, который вы хотите. И.Е. только предоставить текст для опции свернуть.
Теперь вы можете предоставить объект управления как <TextClamp controls={ ... }
. Вот позор объекта управления:
controls = {
expandOptions: {
text: string, // text to display
func: func // func when clicked
},
collapseOptions: {
text: string, // text to display
func: func // func when clicked
}
}
Оба text
и lines
нуждаются в реквизитах.
Text-clamp.js
import React, { PureComponent } from "react";
import v4 from "uuid/v4";
import PropTypes from "prop-types";
import "./Text-clamp.scss"
export default class TextClamp extends PureComponent {
constructor( props ) {
super( props );
// initial state
this.state = {
displayedText: "",
expanded: false
}
// generate uuid
this.id = v4();
// bind this to methods
this.produceLines = this.produceLines.bind( this );
this.handleExpand = this.handleExpand.bind( this );
this.handleCollapse = this.handleCollapse.bind( this );
this.updateDisplayedText = this.updateDisplayedText.bind( this );
this.handleResize = this.handleResize.bind( this );
// setup default controls
this.controls = {
expandOptions: {
text: "Show more...",
func: this.handleExpand
},
collapseOptions: {
text: "Collapse",
func: this.handleCollapse
}
}
// merge default controls with provided controls
if ( this.props.controls ) {
this.controls = mergedControlOptions( this.controls, this.props.controls );
this.handleExpand = this.controls.expandOptions.func;
this.handleCollapse = this.controls.collapseOptions.func;
}
}
componentDidMount() {
// create a div and set some styles that will allow us to measure the width of each
// word in our text
const measurementEl = document.createElement( "div" );
measurementEl.style.visibility = "hidden";
measurementEl.style.position = "absolute";
measurementEl.style.top = "-9999px";
measurementEl.style.left = "-9999px";
measurementEl.style.height = "auto";
measurementEl.style.width = "auto";
measurementEl.style.display = "inline-block";
// get computedStyles so we ensure we measure with the correct font-size and letter-spacing
const computedStyles = window.getComputedStyle( this.textDisplayEl, null );
measurementEl.style.fontSize = computedStyles.getPropertyValue( "font-size" );
measurementEl.style.letterSpacing = computedStyles.getPropertyValue( "letter-spacing" );
// add measurementEl to the dom
document.body.appendChild( measurementEl );
// destructure props
const { text, lines, resize } = this.props;
// reference container, linesToProduce, startAt, and wordArray on this
this.container = document.getElementById( this.id );
this.linesToProduce = lines;
this.startAt = 0;
this.wordArray = text.split( " " );
// measure each word and store reference to their widths
let i, wordArrayLength = this.wordArray.length, wordArray = this.wordArray, wordWidths = { };
for ( i = 0; i < wordArrayLength; i++ ) {
measurementEl.innerHTML = wordArray[ i ];
if ( !wordWidths[ wordArray[ i ] ] ) {
wordWidths[ wordArray[ i ] ] = measurementEl.offsetWidth;
}
}
const { expandOptions } = this.controls;
measurementEl.innerHTML = expandOptions.text;
wordWidths[ expandOptions.text ] = measurementEl.offsetWidth;
measurementEl.innerHTML = " ";
wordWidths[ "WHITESPACE" ] = measurementEl.offsetWidth;
// reference wordWidths on this
this.wordWidths = wordWidths;
// produce lines from ( startAt, maxWidth, wordArray, wordWidths, linesToProduce )
this.updateDisplayedText();
this.resize = resize === false ? reisze : true
// if resize prop is true, enable resizing
if ( this.resize ) {
window.addEventListener( "resize", this.handleResize, false );
}
}
produceLines( startAt, maxWidth, wordArray, wordWidths, linesToProduce, expandOptions ) {
// use _produceLine function to recursively build our displayText
const displayText = _produceLine( startAt, maxWidth, wordArray, wordWidths, linesToProduce, expandOptions );
// update state with our displayText
this.setState({
...this.state,
displayedText: displayText,
expanded: false
});
}
updateDisplayedText() {
this.produceLines(
this.startAt,
this.container.offsetWidth,
this.wordArray,
this.wordWidths,
this.linesToProduce,
this.controls.expandOptions
);
}
handleResize() {
// call this.updateDisplayedText() if not expanded
if ( !this.state.expanded ) {
this.updateDisplayedText();
}
}
handleExpand() {
this.setState({
...this.state,
expanded: true,
displayedText: <span>{ this.wordArray.join( " " ) } - <button
className="_text_clamp_collapse"
type="button"
onClick={ this.handleCollapse }>
{ this.controls.collapseOptions.text }
</button>
</span>
});
}
handleCollapse() {
this.updateDisplayedText();
}
componentWillUnmount() {
// unsubscribe to resize event if resize is enabled
if ( this.resize ) {
window.removeEventListener( "resize", this.handleResize, false );
}
}
render() {
// render the displayText
const { displayedText } = this.state;
return (
<div id={ this.id } className="_text_clamp_container">
<span className="_clamped_text" ref={ ( el ) => { this.textDisplayEl = el } }>{ displayedText }</span>
</div>
);
}
}
TextClamp.propTypes = {
text: PropTypes.string.isRequired,
lines: PropTypes.number.isRequired,
resize: PropTypes.bool,
controls: PropTypes.shape({
expandOptions: PropTypes.shape({
text: PropTypes.string,
func: PropTypes.func
}),
collapseOptions: PropTypes.shape({
text: PropTypes.string,
func: PropTypes.func
})
})
}
function mergedControlOptions( defaults, provided ) {
let key, subKey, controls = defaults;
for ( key in defaults ) {
if ( provided[ key ] ) {
for ( subKey in provided[ key ] ) {
controls[ key ][ subKey ] = provided[ key ][ subKey ];
}
}
}
return controls;
}
function _produceLine( startAt, maxWidth, wordArray, wordWidths, linesToProduce, expandOptions, lines ) {
let i, width = 0;
// format and return displayText if all lines produces
if ( !( linesToProduce > 0 ) ) {
let lastLineArray = lines[ lines.length - 1 ].split( " " );
lastLineArray.push( expandOptions.text );
width = _getWidthOfLastLine( wordWidths, lastLineArray );
width - wordWidths[ "WHITESPACE" ];
lastLineArray = _trimResponseAsNeeded( width, maxWidth, wordWidths, lastLineArray, expandOptions );
lastLineArray.pop();
lines[ lines.length - 1 ] = lastLineArray.join( " " );
let formattedDisplay = <span>{ lines.join( " " ) } - <button
className="_text_clamp_show_all"
type="button"
onClick={ expandOptions.func }>{ expandOptions.text }</button></span>
return formattedDisplay;
}
// increment i until width is > maxWidth
for ( i = startAt; width < maxWidth; i++ ) {
width += wordWidths[ wordArray[ i ] ] + wordWidths[ "WHITESPACE" ];
}
// remove last whitespace width
width - wordWidths[ "WHITESPACE" ];
// use wordArray.slice with the startAt and i - 1 to get the words for the line and
// turn them into a string with .join
let newLine = wordArray.slice( startAt, i - 1 ).join( " " );
// return the production of the next line adding the lines argument
return _produceLine(
i - 1,
maxWidth,
wordArray,
wordWidths,
linesToProduce - 1,
expandOptions,
lines ? [ ...lines, newLine ] : [ newLine ],
);
}
function _getWidthOfLastLine( wordWidths, lastLine ) {
let _width = 0, length = lastLine.length, i;
_width = ( wordWidths[ "WHITESPACE" ] * 2 )
for ( i = 0; i < length; i++ ) {
_width += wordWidths[ lastLine[ i ] ] + wordWidths[ "WHITESPACE" ];
}
return _width;
}
function _trimResponseAsNeeded( width, maxWidth, wordWidths, lastLine, expandOptions ) {
let _width = width,
_maxWidth = maxWidth,
_lastLine = lastLine;
if ( _width > _maxWidth ) {
_lastLine.splice( length - 2, 2 );
_width = _getWidthOfLastLine( wordWidths, _lastLine );
if ( _width > _maxWidth ) {
_lastLine.push( expandOptions.text );
return _trimResponseAsNeeded( _width, _maxWidth, wordWidths, _lastLine, expandOptions );
} else {
_lastLine.splice( length - 2, 2 );
_lastLine.push( expandOptions.text );
if ( _getWidthOfLastLine( wordWidths, lastLine ) > maxWidth ) {
return _trimResponseAsNeeded( _width, _maxWidth, wordWidths, _lastLine, expandOptions );
}
}
} else {
_lastLine.splice( length - 1, 1 );
}
return _lastLine;
}
Текст clamp.scss
._text_clamp_container {
._clamped_text {
._text_clamp_show_all, ._text_clamp_collapse {
background-color: transparent;
padding: 0px;
margin: 0px;
border: none;
color: #2369aa;
cursor: pointer;
&:focus {
outline: none;
text-decoration: underline;
}
&:hover {
text-decoration: underline;
}
}
}
}