web-dev-qa-db-fra.com

Comment faire défiler un React page quand il charge?

Je veux passer une valeur en tant que hachage dans l'URL (myapp.com # quelqueévalue) et faire défiler la page sur cet élément lorsque la page chargée - exactement le comportement de l'utilisation d'un fragment de hachage depuis le début d'Internet. J'ai essayé d'utiliser ScrollINToView mais qui échoue sur iOS. Ensuite, j'ai essayé de simplement déséquilibrer/définir la fenêtre.Location.Location.europa, mais il semble y avoir une condition de race. 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 un retard inutile. Lorsque le délai est trop court, il semble faire défiler jusqu'à l'élément souhaité, mais fait défiler vers le haut de la page. Vous ne verrez pas l'effet sur cette démo, mais cela arrive dans mon application réelle https: //codesandbox.io/s/pjok544nrx Ligne 75

  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);
    }
  }

EDIT: J'ai suivi la ligne de React qui reduit la page et réinitialise par conséquent la position de défilement après la défilement déjà défilée où je l'ai dit d'aller dans la composanteDidMount (). C'est Celui-ci . style[styleName] = styleValue; Au moment où la page est re-rendue, il définit la propriété de style de largeur du composant INPPUTBOX du composant Box Select React. La trace de la pile juste avant que cela ressemble à

(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

Je ne sais pas où déplacer mes instructions pour faire défiler la page. Cela doit se produire après que ces styles soient définis qui se produisent après le comimedididmount ().

EDIT: J'ai besoin de mieux clarifier exactement ce dont j'ai besoin pour faire. Toutes mes excuses pour ne pas le faire avant.

Incontournables

Cela doit fonctionner sur tous les appareils de bureau et mobiles communs.

Lorsque la page se charge, il pourrait y avoir trois situations en fonction de la requête fournie dans l'URL après le #:

  • 1) Aucune requête n'a été fournie - rien ne se passe
  • 2) une requête valide a été fournie - la page fait défiler instantanément à l'ancre souhaitée et le titre de la page passe à l'étiquette de l'option.
  • 3) une requête non valide a été fournie - la requête est entrée dans la zone de recherche, la zone de recherche est focalisée et le menu s'ouvre avec des options limitées par la requête fournie comme si elles avaient été saisies. Le curseur est situé dans la zone de recherche de la fin de la requête afin que l'utilisateur puisse modifier la requête.

Une requête valide est celle qui correspond à la valeur de l'une des options. Une requête invalide est celle qui ne le fait pas.

Les boutons de dos et d'avant 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 doit faire défiler instantanément sur cette ancrage, la requête doit effacer le retour à l'espace réservé "Rechercher ...", l'URL doit mettre à jour et le titre du document doit passer à l'étiquette de l'option.

Facultatif mais serait génial

Après une sélection, le menu se ferme et la requête remonte à "Rechercher ...", et soit

  • la zone de recherche conserve l'objectif pour que l'utilisateur puisse commencer à taper une autre requête. La prochaine fois qu'il est cliqué , le menu s'ouvre et la requête de la recherche de la recherche avant la sélection, le filtre du menu et la position de défilement du menu sont restaurées (la plupart préféré), ou
  • la boîte de recherche perd la mise au point. La prochaine fois que c'est axé , le menu s'ouvre et la requête de la zone de recherche d'avant la sélection, le filtre du menu et la position de défilement du menu sont restaurées ( préféré), ou
  • la zone de recherche conserve l'objectif pour que l'utilisateur puisse commencer à taper une autre requête. La position de la requête antérieure et du menu sont perdues.
8
Dan Cancro

Probablement plus facile à faire cela avec le react-scrollable-anchor une bibliothèque:

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 travailler en allant ici: https://zwoj0xw503.codesandbox.io/#doge

2
Colin Ricardo

Vous pouvez simplifier votre code à l'aide de react-router-dom 'S - hashrouder avec réact ref.current.scrollIntoView() ou window.scrollTo(ref.currrent.offsetTop).

Actuellement testé et travaillant sur Chrome (bureau/Android), Firefox (bureau), navigateur de soie (Android), Saumsung Internet (Android) et Safari (iOS - avec une mise en garde en ce que Saute instantanément à la sélection). Pendant que j'ai pu tester et confirmer que cela fonctionne dans l'environnement de sandbox iframe, vous devrez vous adapter/le tester dans un iframe.

J'aimerais également souligner que votre approche est très jQuery comme et que vous devriez éviter de tenter directement d'utiliser ou de manipuler le DOM et/ou le window. De plus, vous semblez utiliser beaucoup de variables let tout au long de votre code, mais elles restent inchangées. Au lieu de cela, ils devraient être un const variable as React évalue les variables à chaque fois que cela renouille, de sorte que vous ne prévoyez de changer cette variable pendant le processus de re-rendu, il n'y a pas de besoin d'utiliser let.

Exemple de travail : https://v6y5211n4l.codesandbox.io

Edit ScrollIntoView on Selection


Composants/barre de défilement

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);

Index.js

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"));
2
Matt Carlotta