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.
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 #:
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.
Après une sélection, le menu se ferme et la requête remonte à "Rechercher ...", et soit
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
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
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"));