子div
sの垂直リストを含むdiv
であるポップアップリストがあります。上下のキーボードナビゲーションを追加して、現在強調表示されている子を変更しました。
今、下キーを十分に押すと、強調表示されたアイテムは表示されなくなります。ビューがスクロールされた場合、上キーでも同じことが発生します。
Reactで子div
を自動的にスクロールして表示する正しい方法は何ですか?
ある種のList
コンポーネントとある種のItem
コンポーネントがあると仮定します。私がそれをした方法 1つのプロジェクトで は、アイテムがアクティブかどうかを知らせることでした。アイテムは、必要に応じてリストにスクロールして表示するように要求します。次の擬似コードを検討してください。
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
return <Item key={item.id} item={item}
active={item.id === this.props.activeId}
scrollIntoView={this.scrollElementIntoViewIfNeeded} />
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
class Item extends React.Component {
render() {
return <div>something...</div>;
}
componentDidMount() {
this.ensureVisible();
}
componentDidUpdate() {
this.ensureVisible();
}
ensureVisible() {
if (this.props.active) {
this.props.scrollIntoView(React.findDOMNode(this));
}
}
}
より良い解決策は、おそらく、アイテムをスクロールして表示する責任をリストに持たせることです(アイテムがリストにあることを認識せずに)。これを行うには、特定のアイテムにref
属性を追加し、それでそれを見つけることができます。
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
var active = item.id === this.props.activeId;
var props = {
key: item.id,
item: item,
active: active
};
if (active) {
props.ref = "activeItem";
}
return <Item {...props} />
}
componentDidUpdate(prevProps) {
// only scroll into view if the active item changed last render
if (this.props.activeId !== prevProps.activeId) {
this.ensureActiveItemVisible();
}
}
ensureActiveItemVisible() {
var itemComponent = this.refs.activeItem;
if (itemComponent) {
var domNode = React.findDOMNode(itemComponent);
this.scrollElementIntoViewIfNeeded(domNode);
}
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
リストノード内でアイテムが表示されるかどうかを判断する計算を行いたくない場合は、 DOMメソッドscrollIntoView()
またはWebkit固有のscrollIntoViewIfNeeded
を使用できます。 、これは ポリフィルが使用可能 であるため、非Webkitブラウザーで使用できます。
文字列ではなくrefで関数を使用する別の例
class List extends React.Component {
constructor(props) {
super(props);
this.state = { items:[], index: 0 };
this._nodes = new Map();
this.handleAdd = this.handleAdd.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleAdd() {
let startNumber = 0;
if (this.state.items.length) {
startNumber = this.state.items[this.state.items.length - 1];
}
let newItems = this.state.items.splice(0);
for (let i = startNumber; i < startNumber + 100; i++) {
newItems.Push(i);
}
this.setState({ items: newItems });
}
handleRemove() {
this.setState({ items: this.state.items.slice(1) });
}
handleShow(i) {
this.setState({index: i});
const node = this._nodes.get(i);
console.log(this._nodes);
if (node) {
ReactDOM.findDOMNode(node).scrollIntoView({block: 'end', behavior: 'smooth'});
}
}
render() {
return(
<div>
<ul>{this.state.items.map((item, i) => (<Item key={i} ref={(element) => this._nodes.set(i, element)}>{item}</Item>))}</ul>
<button onClick={this.handleShow.bind(this, 0)}>0</button>
<button onClick={this.handleShow.bind(this, 50)}>50</button>
<button onClick={this.handleShow.bind(this, 99)}>99</button>
<button onClick={this.handleAdd}>Add</button>
<button onClick={this.handleRemove}>Remove</button>
{this.state.index}
</div>
);
}
}
class Item extends React.Component
{
render() {
return (<li ref={ element => this.listItem = element }>
{this.props.children}
</li>);
}
}
@Michelle Tilleyの答えに基づいて、ユーザーの選択が変更された場合にスクロールしたいことがあるので、componentDidUpdate
でスクロールをトリガーします。また、スクロールする距離とスクロールが必要かどうかを計算するために、いくつかの計算を行いました。
componentDidUpdate() {
let panel, node;
if (this.refs.selectedSection && this.refs.selectedItem) {
// This is the container you want to scroll.
panel = this.refs.listPanel;
// This is the element you want to make visible w/i the container
// Note: You can nest refs here if you want an item w/i the selected item
node = ReactDOM.findDOMNode(this.refs.selectedItem);
}
if (panel && node &&
(node.offsetTop > panel.scrollTop + panel.offsetHeight || node.offsetTop < panel.scrollTop)) {
panel.scrollTop = node.offsetTop - panel.offsetTop;
}
}
React 16の場合、正しい答えは以前の答えとは異なります。
class Something extends Component {
constructor(props) {
super(props);
this.boxRef = React.createRef();
}
render() {
return (
<div ref={this.boxRef} />
);
}
}
次に、スクロールするには、(コンストラクタの後に)追加するだけです:
componentDidMount() {
if (this.props.active) { // whatever your test might be
this.boxRef.current.scrollIntoView();
}
}
注:「.current」を使用する必要があり、オプションをscrollIntoViewに送信できます。
scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
( http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/ で見つかりました)
仕様を読んで、ブロックとインラインの意味を理解するのは少し困難でしたが、それで遊んだ後、垂直スクロールリストの場合、ブロック: 'end'はトップを人為的にスクロールせずに要素が表示されることを確認しましたビューポート以外のコンテンツの「中央」では、下部近くの要素が大きくスライドし、その下に空のスペースが表示されます。しかし、私のコンテナーは、justify: 'stretch'を持つflex親であり、動作に影響を与える可能性があります。私はこれ以上掘りませんでした。オーバーフローが非表示の要素はscrollIntoViewの動作に影響を与えるため、おそらく自分で実験する必要があります。
私のアプリケーションには、親が表示されている必要があり、子が選択されている場合は、スクロールして表示されます。親のDidMountは子のDidMountの前に発生するため、これはうまく機能しました。したがって、親までスクロールし、アクティブな子がレンダリングされると、さらにスクロールしてその子を表示します。
誰かがここでつまずいた場合に備えて、私はこのようにしました
componentDidMount(){
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
componentDidUpdate() {
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
render() {
return (
<div>
{messages.map((msg, index) => {
return (
<Message key={index} msgObj={msg}
{/*<p>some test text</p>*/}
</Message>
)
})}
<div style={{height: '30px'}} id='#tracker' ref="trackerRef"></div>
</div>
)
}
scrollIntoView
はネイティブDOM機能です link
常にtracker
divが表示されます
キーアップ/ダウンハンドラーでは、スクロールするdivのscrollTop
プロパティを設定するだけで、下(または上)にスクロールできます。
例えば:
JSX:
<div ref="foo">{content}</div>
キーアップ/ダウンハンドラー:
this.refs.foo.getDOMNode().scrollTop += 10
上記と同様の操作を行うと、divは10ピクセル下にスクロールします(divがcssでauto
またはscroll
にオーバーフローするように設定されており、当然コンテンツがオーバーフローしています)。
これを展開して、divを下にスクロールするスクロールdiv内の要素のオフセットを見つけ、scrollTop
を変更して、要素の高さに基づいて要素が表示されるように十分にスクロールする必要があります。
MDNのscrollTopとoffsetTopの定義をご覧ください:
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
名前付きアンカーのように、クリックしたときにその要素にスクロールするNavLinkがありました。この方法で実装しました。
<NavLink onClick={() => this.scrollToHref('plans')}>Our Plans</NavLink>
scrollToHref = (element) =>{
let node;
if(element === 'how'){
node = ReactDom.findDOMNode(this.refs.how);
console.log(this.refs)
}else if(element === 'plans'){
node = ReactDom.findDOMNode(this.refs.plans);
}else if(element === 'about'){
node = ReactDom.findDOMNode(this.refs.about);
}
node.scrollIntoView({block: 'start', behavior: 'smooth'});
}
次に、スクロールしたいコンポーネントをこのような参照に渡します
<Investments ref="plans"/>
ReactでScroll-To機能を検索している他のユーザー向けに、もう少し情報を追加しています。私はアプリのScroll-Toを行うためにいくつかのライブラリを結び付けていましたが、react-scrollchorが見つかるまでユースケースから機能しなかったため、それを渡すと思いました。 https://github.com/bySabi/react-scrollchor