Je veux passer une valeur comme hachage dans l'url (myapp.com # somevalue) et faire défiler la page jusqu'à cet élément lorsque la page se charge - exactement le comportement de l'utilisation d'un fragment de hachage depuis le début d'Internet. J'ai essayé d'utiliser scrollIntoView mais cela échoue sur iOS. Ensuite, j'ai juste essayé de désactiver / paramétrer window.location.hash mais il semble y avoir une condition de concurrence. Cela ne fonctionne que lorsque le délai est supérieur à 600 ms.
Je voudrais une solution plus solide et je ne veux pas introduire de retard inutile. Lorsque le délai est trop court, il semble défiler jusqu'à l'élément souhaité, puis défile vers le haut de la page. Vous ne verrez pas l'effet sur cette démo, mais cela se produit dans mon application actuelle https://codesandbox.io/s/ pjok544nrx line 75
(anonymous) @ VM38319:1 setValueForStyles @ react-dom.development.js:6426 updateDOMProperties @ react-dom.development.js:7587 updateProperties @ react-dom.development.js:7953 commitUpdate @ react-dom.development.js:8797 commitWork @ react-dom.development.js:17915 commitAllHostEffects @ react-dom.development.js:18634 callCallback @ react-dom.development.js:149 invokeGuardedCallbackDev @ react-dom.development.js:199 invokeGuardedCallback @ react-dom.development.js:256 commitRoot @ react-dom.development.js:18867 (anonymous) @ react-dom.development.js:20372 unstable_runWithPriority @ scheduler.development.js:255 completeRoot @ react-dom.development.js:20371 performWorkOnRoot @ react-dom.development.js:20300 performWork @ react-dom.development.js:20208 performSyncWork @ react-dom.development.js:20182 requestWork @ react-dom.development.js:20051 scheduleWork @ react-dom.development.js:19865 scheduleRootUpdate @ react-dom.development.js:20526 updateContainerAtExpirationTime @ react-dom.development.js:20554 updateContainer @ react-dom.development.js:20611 ReactRoot.render @ react-dom.development.js:20907 (anonymous) @ react-dom.development.js:21044 unbatchedUpdates @ react-dom.development.js:20413 legacyRenderSubtreeIntoContainer @ react-dom.development.js:21040 render @ react-dom.development.js:21109 (anonymous) @ questionsPageIndex.jsx:10 ./src/client/questionsPageIndex.jsx @ index.html:673 __webpack_require__ @ index.html:32 (anonymous) @ index.html:96 (anonymous) @ index.html:99
EDIT: J'ai suivi la ligne de React qui restitue la page et par conséquent réinitialise la position de défilement après avoir déjà défilé là où je l'ai dit pour aller dans componentDidMount (). C'est celui-ci .
style [styleName] = styleValue;
Au moment du rendu de la page, il définit la propriété de style width du composant inputbox du composant react-select box. La trace de pile juste avant cela ressemble à
componentDidMount() { let self = this; let updateSearchControl = hash => { let selectedOption = self.state.searchOptions.filter( option => option.value === hash )[0]; if (selectedOption) { // this doesn't work with Safari on iOS // document.getElementById(hash).scrollIntoView(true); // this works if delay is 900 but not 500 ms setTimeout(() => { // unset and set the hash to trigger scrolling to target window.location.hash = null; window.location.hash = hash; // scroll back by the height of the Search box window.scrollBy( 0, -document.getElementsByClassName("heading")[0].clientHeight ); }, 900); } else if (hash) { this.searchRef.current.select.focus(); } }; // Get the hash // I want this to work as a Google Apps Script too which runs in // an iframe and has a special way to get the hash if (!!window["google"]) { let updateHash = location => { updateSearchControl(location.hash); }; eval("google.script.url.getLocation(updateHash)"); } else { let hash = window.location.hash.slice(1); updateSearchControl(hash); } }
Je ne sais pas où déplacer mes instructions pour faire défiler la page. Cela doit se produire après la définition de ces styles, ce qui se produit après componentDidMount ().
EDIT: J'ai besoin de mieux clarifier exactement ce que j'ai besoin de faire. Toutes mes excuses pour ne pas l'avoir fait auparavant.
Cela doit fonctionner sur tous les ordinateurs de bureau et appareils mobiles courants.
Lorsque la page se charge, il peut y en avoir trois situations en fonction de la requête fournie dans l'url après le #:
Une requête valide est celle qui correspond à la valeur de l'une des options. Une requête non valide en est une qui ne l'est pas.
Les boutons d'arrière-plan du navigateur doivent déplacer la position de défilement en conséquence.
Chaque fois qu'une option est choisie dans la zone de recherche, la page faites défiler instantanément jusqu'à cette ancre, la requête doit s'effacer en retournant à l'espace réservé "Rechercher ...", l'url doit se mettre à jour et le titre du document doit devenir le libellé de l'option.
Après avoir fait une sélection, le menu se ferme et la requête revient à "Rechercher ...", et soit
4 Réponses :
Vous pouvez utiliser https://www.npmjs.com/package/element. scrollintoviewifneeded-polyfill .
Dès que possible dans votre application, importez la bibliothèque
ref.current.scrollIntoViewIfNeeded();
Ensuite, vous pouvez utiliser la méthode scrollIntoViewIfNeeded (); sur tous les nœuds dom.
La bibliothèque polyfill la méthode afin que vous puissiez l'utiliser sur tous les navigateurs.
Je vous recommande de l'utiliser avec un react ref
afin que vous n'ayez pas à appeler getElementById ou autre document afin de récupérer le nœud dom.
const ref = React.createRef(); <div id="somevalue" ref={ref} />
Dans votre componentDidMount
, vous pouvez utiliser la même méthode que vous décrivez pour récupérer l'id du div que vous voulez faire défiler, obtenir la référence associée et appeler
import 'element.scrollintoviewifneeded-polyfill';
Lorsque la référence est la bonne référence de div, vous pouvez évidemment stocker toutes les références dans une carte et récupérer dans vos méthodes de composants.
Merci. Malheureusement, il le fait toujours. Il saute au bon endroit, puis revient en haut. github.com/dancancro/ questions / blob / nodiv / src / client / compone nts /…
Probablement plus facile à faire avec la bibliothèque react-scrollable-anchor
:
import React from "react"; import ReactDOM from "react-dom"; import ScrollableAnchor from "react-scrollable-anchor"; class App extends React.Component { render() { return ( <React.Fragment> <div style={{ marginBottom: 7000 }}>nothing to see here</div> <div style={{ marginBottom: 7000 }}>never mind me</div> <ScrollableAnchor id={"doge"}> <div>bork</div> </ScrollableAnchor> </React.Fragment> ); } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
Vous pouvez le voir fonctionner en allant ici: https://zwoj0xw503.codesandbox.io/#doge
En interne, ScrollableAnchors effectue le défilement en définissant window.location.hash, de sorte qu'il a la même réponse à la question Quelle et ne répond pas à la question Quand. Si c'est effectivement une question pour TC39 comme il semble, et étant donné que je n'ai pas immédiatement reçu de réponses instantanées que la capture de "l'événement prêt à être défilé" est impossible, je dois violer un principe de base de la conception Web sur mon premier projet, mais c'est vraiment difficile à accepter. Cela semble être une exigence éminemment raisonnable. github.com/gabergg/ react-scrollable-anchor / blob / master / src /…
Vous pouvez simplifier votre code en utilisant HashRouter avec le ref.current.scrollIntoView ()
ou window.scrollTo (ref .currrent.offsetTop)
.
Actuellement testé et fonctionne sur Chrome (bureau / Android), Firefox (bureau), Silk Browser (Android), Saumsung Internet (Android) et Safari (iOS - avec une mise en garde en ce sens qu'il passe instantanément à la sélection) . Bien que j'ai pu tester et confirmer qu'il fonctionne dans l ' iframe
de l'environnement sandbox, vous devrez l'adapter / le tester dans un iframe
autonome.
Je tiens également à souligner que votre approche ressemble beaucoup à jQuery
et que vous devez éviter de tenter directement d'utiliser ou de manipuler le DOM
et / ou le fenêtre
. De plus, vous semblez utiliser beaucoup de variables let
dans tout votre code, mais elles restent inchangées. Au lieu de cela, ils devraient être une variable const
car React évalue les variables à chaque fois qu'il effectue un nouveau rendu, donc à moins que vous ne prévoyiez de changer cette variable pendant le processus de rendu, il n'est pas nécessaire d'utiliser let
.
Exemple de travail : https://v6y5211n4l.codesandbox.io a>
components/ScrollBar
import React from "react"; import { render } from "react-dom"; import { HashRouter } from "react-router-dom"; import Helmet from "react-helmet"; import ScrollBar from "../src/components/ScrollBar"; const config = { htmlAttributes: { lang: "en" }, title: "My App", titleTemplate: "Scroll Search - %s", meta: [ { name: "description", content: "The best app in the world." } ] }; const searchOptions = [ { label: "apple", value: "apple" }, { label: "orange", value: "orange" }, { label: "banana", value: "banana" }, { label: "strawberry", value: "strawberry" } ]; const styles = { menu: base => ({ ...base, width: "100%", height: "75vh" }), menuList: base => ({ ...base, minHeight: "75vh" }), option: base => ({ ...base, color: "blue" }) }; const App = () => ( <HashRouter> <Helmet {...config} /> <ScrollBar searchOptions={searchOptions} styles={styles} /> </HashRouter> ); render(<App />, document.getElementById("root"));
index.js
import React, { Component } from "react"; import PropTypes from "prop-types"; import Helmet from "react-helmet"; import Select from "react-select"; import { withRouter } from "react-router-dom"; import "./styles.css"; const scrollOpts = { behavior: "smooth", block: "start" }; class ScrollBar extends Component { constructor(props) { super(props); const titleRefs = props.searchOptions.map( ({ label }) => (this[label] = React.createRef()) ); // map over options and use "label" as a ref name; ex: this.orange this.topWindow = React.createRef(); this.searchRef = React.createRef(); const pathname = props.history.location.pathname.replace(/\//g, ""); // set current pathname without the "/" this.state = { titleRefs, menuIsOpen: false, isValid: props.searchOptions.some(({ label }) => label === pathname), // check if the current pathname matches any of the labels pathname }; } componentDidMount() { const { pathname, isValid } = this.state; if (isValid) this.scrollToElement(); // if theres a pathname and it matches a label, scroll to it if (!isValid && pathname) this.searchRef.current.select.focus(); // if there's a pathname but it's invalid, focus on the search bar } // allows us to update state based upon prop updates (in this case // we're looking for changes in the history.location.pathname) static getDerivedStateFromProps(props, state) { const nextPath = props.history.location.pathname.replace(/\//g, ""); const isValid = props.searchOptions.some(({ label }) => label === nextPath); return state.pathname !== nextPath ? { pathname: nextPath, isValid } : null; // if the path has changed, update the pathname and whether or not it is valid } // allows us to compare new state to old state (in this case // we're looking for changes in the pathname) componentDidUpdate(prevProps, prevState) { if (this.state.pathname !== prevState.pathname) { this.scrollToElement(); } } scrollToElement = () => { const { isValid, pathname } = this.state; const elem = isValid ? pathname : "topWindow"; setTimeout(() => { window.scrollTo({ behavior: "smooth", top: this[elem].current.offsetTop - 45 }); }, 100); }; onInputChange = (options, { action }) => { if (action === "menu-close") { this.setState({ menuIsOpen: false }, () => this.searchRef.current.select.blur() ); } else { this.setState({ menuIsOpen: true }); } }; onChange = ({ value }) => { const { history } = this.props; if (history.location.pathname.replace(/\//g, "") !== value) { // if the select value is different than the previous selected value, update the url -- required, otherwise, react-router will complain about pushing the same pathname/value that is current in the url history.push(value); } this.searchRef.current.select.blur(); }; onFocus = () => this.setState({ menuIsOpen: true }); render() { const { pathname, menuIsOpen, titleRefs } = this.state; const { searchOptions, styles } = this.props; return ( <div className="container"> <Helmet title={this.state.pathname || "Select an option"} /> <div className="heading"> <Select placeholder="Search..." isClearable={true} isSearchable={true} options={searchOptions} defaultInputValue={pathname} onFocus={this.onFocus} onInputChange={this.onInputChange} onChange={this.onChange} menuIsOpen={menuIsOpen} ref={this.searchRef} value={null} styles={styles || {}} /> </div> <div className="fruits-list"> <div ref={this.topWindow} /> {searchOptions.map(({ label, value }, key) => ( <div key={value} id={value}> <p ref={titleRefs[key]}>{label}</p> {[1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14].map(num => ( <p key={num}>{num}</p> ))} </div> ))} </div> </div> ); } } ScrollBar.propTypes = { searchOptions: PropTypes.arrayOf( PropTypes.shape({ label: PropTypes.string.isRequired, value: PropTypes.string.isRequired }).isRequired ).isRequired, styles: PropTypes.objectOf(PropTypes.func) }; export default withRouter(ScrollBar);
C'est génial merci. Il gère bien le cas où une valeur valide est fournie, mais je veux également gérer le cas où une valeur invalide l'est. J'ai clarifié les détails ci-dessus et commencé à travailler sur une modification de votre solution ici codesandbox.io/s/xl46pnmr8p
Réponse mise à jour ci-dessus. De plus, le setTimeout
est requis pour les navigateurs mobiles. Sinon, il ne défilera pas.
Juste fini. Cela fonctionne sur mon ordinateur mais cela ne fonctionne pas dans Safari sur mon iPhone. Il défilera jusqu'au bon endroit lors du chargement, mais la zone de recherche disparaît. Si vous faites glisser manuellement tout le chemin vers le haut, puis balayez encore une fois, la boîte revient. La zone de recherche reste en place si vous faites défiler manuellement mais que scrollIntoView () ne fonctionne pas sur cet appareil. Veuillez essayer ceci sur mobile: v6y5211n4l.codesandbox.io/#/orange
Réponse mise à jour. C'est un problème avec le mobile qui n'aime pas overflow: hidden;
sur le body
et position: absolute;
sur le Select
composant. Au lieu de cela, définissez .heading
comme position: fixed; width: 100%;
et supprimez le overflow: hidden;
. En tant que tel, vous devrez utiliser un window.scrollTo ()
avec un calcul offsetTop
, qui défilera jusqu'à l'emplacement correct (puisqu'une position fixe flotte au-dessus, vous 'devra le compenser, sinon il atterrira directement au-dessus de la ref
). De plus, un autre inconvénient est que lorsque vous utilisez le bouton retour
, il saute immédiatement au dernier emplacement sélectionné.
Quelle que soit la direction que vous prenez, ref.scrollIntoView ()
ou window.scrollTo ()
vous allez rencontrer des limitations de navigateur.
Je mets ceci juste après les importations dans le fichier de mon composant:
componentDidMount() { const { pathname, isValid } = this.state; if(!isValid && pathname) { // if there's a pathname but it's invalid, this.searchRef.current.select.focus(); // focus on the search bar } initialValidSlug = pathname; // sets the value to be used in the $.ready() above }
et l'ai pour mon componentDidMount()
:
let initialValidSlug = ''; window.addEventListener('load', () => { window.location.hash = ''; window.location.hash = (initialValidSlug ? initialValidSlug : ''); window.scrollBy(0, -document.getElementsByClassName("heading")[0].clientHeight); console.log('window.addEventListener') });
solution possible - stackoverflow .com / questions / 3163615 /…
peut-être que useEffect vaut la peine d'essayer. si je comprends bien, useEffect fonctionnera après le rendu soumis à l'écran et à ce moment l'élément est prêt à