Airbnbのようにマップを作成する方法とGoogleマップ
16606 ワード
導入
免責事項
タスク
それがAirbnbの上でされるように、我々がアパートカードをそれに表示している地図をつくる必要があると仮定しましょう
次のテクノロジスタックです.
実装
アプリを作成
すべてはかなり些細な-あなたはアプリケーションをインストールする必要があります
npx create-react-app my-app --template typescript
インストール依存
アプリケーションが動作するには、私たちは、mui @ gogglemaps/反応ラッパーが必要です
npm install --save @material-ui/core @material-ui/icons @googlemaps/react-wrapper
設定マップ
このステップでは、アプリケーションに簡単なマップを統合します.まず最初に、Googleマップキーを得る必要があります.
まず、Googleマップのラッパーになるマップコンポーネントを作成しましょう.
import { useEffect, useRef, useState } from "react";
// we will use make styles for styling components, you can use another solutions (like css, sass or cssonjs
import { makeStyles } from "@material-ui/core";
// api mock data
import Apartments from "./apartments";
// Our component will receive center coords and zoom size in props
type MapProps = {
center: google.maps.LatLngLiteral
zoom: number
}
// map wrapper styles
const useStyles = makeStyles({
map: {
height: '100vh'
}
})
function Map({ center, zoom }: MapProps) {
const ref = useRef(null);
const [map, setMap] = useState<google.maps.Map<Element> | null>(null)
const classes = useStyles();
useEffect(() => {
// we need to save google-map object for adding markers and routes in future
if (ref.current) {
// here will connect map frame to div element in DOM by using ref hook
let createdMap = new window.google.maps.Map(
ref.current,
{
center,
zoom,
disableDefaultUI: true,
clickableIcons: false
}
);
setMap(createdMap)
}
}, [center, zoom]);
// map will be connect to this div block
return <div ref={ref} id="map" className={classes.map} />;
}
export default Map
次に、アプリを変更してみましょう.tsx with:import React, { ReactElement } from 'react';
import { Wrapper, Status } from "@googlemaps/react-wrapper";
import Map from './Map'
// Here we can add views when map will loading or failure
const render = (status: Status): ReactElement => {
if (status === Status.LOADING) return <h3>{status} ..</h3>;
if (status === Status.FAILURE) return <h3>{status} ...</h3>;
return <></>;
};
function App() {
if (!process.env.REACT_APP_GOOGLE_KEY) {
return <h2>Add google key</h2>
}
return (
<div className="App">
<Wrapper apiKey={process.env.REACT_APP_GOOGLE_KEY} render={render}>
<Map center={{ lat: 55.753559, lng: 37.609218 }} zoom={11} />
</Wrapper>
</div>
);
}
export default App;
結果は以下の通りです:https://developers.google.com/maps/documentation/javascript/get-api-key
カスタムオーバーレイを追加
次のステップはカスタムオーバーレイを追加することです.なぜ我々はマーカーを使用しないオーバーレイを使用しますか?私の意見では、定期的なマーカーをカスタマイズするのは難しいでしょう、ドキュメントによって、私たちはアイコンイメージだけを変えることができて、それの上にラベルをつけることができます.
重ね合わせコンテナーを作成しましょう.そして、それは特定の座標で地図に位置するコンポーネントのためのラッパーであるでしょう.
import * as React from 'react'
import ReactDOM from 'react-dom';
// base function for creating DOM div node
function createOverlayElement() {
const el = document.createElement('div');
el.style.position = 'absolute';
el.style.display = 'inline-block';
el.style.width = '9999px';
return el;
}
// Our OverlayComponent will recieve map, postion and children props - position is coords, map is google.map object and children is a component that will be render in overlay
export type Props = {
map: google.maps.Map | null
position: { lat: number, lng: number }
children?: React.ReactChild
}
const OverlayContainer = (props: Props) => {
const overlay = React.useRef<google.maps.OverlayView | null>(null)
const el = React.useRef<Element | null>(null)
// modified OverlayView from google.maps [https://developers.google.com/maps/documentation/javascript/reference/3.44/overlay-view?hl=en]
class OverlayView extends window.google.maps.OverlayView {
position: google.maps.LatLng | null = null;
content: any = null;
constructor(props: any) {
super();
props.position && (this.position = props.position);
props.content && (this.content = props.content);
}
onAdd = () => {
if (this.content) this.getPanes().floatPane.appendChild(this.content);
};
onRemove = () => {
if (this.content?.parentElement) {
this.content.parentElement.removeChild(this.content);
}
};
draw = () => {
if (this.position) {
const divPosition = this.getProjection().fromLatLngToDivPixel(
this.position
);
this.content.style.left = divPosition.x + 'px';
this.content.style.top = divPosition.y + 'px';
}
};
}
React.useEffect(() => {
return () => {
if (overlay.current) overlay.current.setMap(null)
}
}, [])
if (props.map) {
el.current = el.current || createOverlayElement()
overlay.current = overlay.current || new OverlayView(
{
position: new google.maps.LatLng(props.position.lat, props.position.lng),
content: el.current
}
)
overlay.current.setMap(props.map)
return ReactDOM.createPortal(props.children, el.current);
}
return null
}
export default OverlayContainer
マップポイントとアパートカードの作成
私は、Mui CoreとMUIアイコンを使用して簡単なアパートカードを作成します.
カードを作成しましょう
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Typography from '@material-ui/core/Typography';
import AspectRatioIcon from '@material-ui/icons/AspectRatio';
import { Grid, IconButton } from '@material-ui/core';
import MeetingRoomIcon from '@material-ui/icons/MeetingRoom';
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp';
import CloseIcon from '@material-ui/icons/Close';
const useStyles = makeStyles({
root: {
maxWidth: 230,
position: 'relative',
zIndex: 1001,
},
media: {
height: 100,
},
close: {
position: 'absolute',
left: 0,
top: 0,
zIndex: 1001,
background: 'white',
width: '25px',
height: '25px'
}
});
type ApartmentCardProps = {
image: string
address: string
area: number
rooms_number: number
floor: number
floor_count: number
rent: number
handleClose: () => void
}
export default function ApartmentCard(props: ApartmentCardProps) {
const classes = useStyles();
return (
<Card className={classes.root}>
<IconButton className={classes.close} aria-label="close" onClick={props.handleClose}>
<CloseIcon />
</IconButton>
<CardActionArea>
<CardMedia
className={classes.media}
image={props.image}
title="Contemplative Reptile"
/>
<CardContent>
<Typography variant="body2" component="h2">
{props.address}
</Typography>
<Grid container spacing={1}>
<Grid item container xs={6} spacing={1} alignItems='center'>
<Grid item xs={8}><AspectRatioIcon /></Grid>
<Grid item xs={4}>{props.area}</Grid>
</Grid>
<Grid item container xs={6} spacing={1} alignItems='center'>
<Grid item xs={8}><MeetingRoomIcon /></Grid>
<Grid item xs={4}>{props.rooms_number}</Grid>
</Grid>
<Grid item container xs={6} spacing={1} alignItems='center'>
<Grid item xs={8}><KeyboardArrowUpIcon /></Grid>
<Grid item xs={4}>{props.floor}/{props.floor_count}</Grid>
</Grid>
<Grid item container xs={12} spacing={1} alignItems='center' justifyContent="center">
<Typography variant="body2" style={{ fontWeight: 600 }}>{props.rent} $</Typography>
</Grid>
</Grid>
</Typography> */}
</CardContent>
</CardActionArea>
</Card>
);
}
と接続点:import { makeStyles } from "@material-ui/styles"
type ApartmentPonitProps = {
price: number
onClick: () => void
}
const styles = makeStyles({
root:{
background: 'white',
borderRadius: '12px',
padding: '8px',
width: '60px',
zIndex: 1000,
position: 'relative'
}
})
const ApartmentPoint = (props: ApartmentPonitProps) => {
const classes = styles()
return (
<div className={classes.root} onClick={props.onClick}>
{props.price} $
</div>
)
}
export default ApartmentPoint
ApammentPointまたはAtsolmentCardをレンダリングするmappoint - likeラッパーを使用します.import { useEffect, useRef, useState } from "react"
import ApartmentCard from "./ApartmentCard"
import ApartmentPoint from "./ApartmentPoint"
type MapPointProps = {
image: string
address: string
area: number
rooms_number: number
floor: number
floor_count: number
rent: number
}
const MapPoint = (props: MapPointProps) => {
const [opened, setIsOpened] = useState<boolean>(false)
const handleOnOpen = () => setIsOpened(true)
const handleOnClose = () => setIsOpened(false)
const containerRef = useRef<HTMLDivElement>(null)
// Hook for handle outside click - simple implementation from stack overflow
useEffect(() => {
function handleClickOutside(this: Document, event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpened(false)
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [containerRef]);
return (<div ref={containerRef}>
{opened ?
<ApartmentCard
image={props.image}
address={props.address}
area={props.area}
rooms_number={props.rooms_number}
floor={props.floor}
floor_count={props.floor_count}
rent={props.rent}
handleClose={handleOnClose}
/> :
<ApartmentPoint
price={props.rent}
onClick={handleOnOpen}
/>}
</div>)
}
export default MapPoint
すべて一緒に
オーバーレイコンテナ内のアパートメントポイントを追加して、マップコンポーネントを変更します
import { makeStyles } from "@material-ui/core";
import { useEffect, useRef, useState } from "react";
import Apartments from "./apartments";
import MapPoint from "./MapPoint";
import OverlayContainer from "./OverlayContainer";
type MapProps = {
center: google.maps.LatLngLiteral
zoom: number
}
const useStyles = makeStyles({
map: {
height: '100vh'
}
})
function Map({ center, zoom }: MapProps) {
const ref = useRef(null);
const [map, setMap] = useState<google.maps.Map<Element> | null>(null)
const classes = useStyles();
useEffect(() => {
if (ref.current) {
let createdMap = new window.google.maps.Map(
ref.current,
{
center,
zoom,
disableDefaultUI: true,
clickableIcons: false
}
);
setMap(createdMap)
}
}, [center, zoom]);
return <div ref={ref} id="map" className={classes.map}>
{Apartments.map((apartment, index) => (
<OverlayContainer
map={map}
position={{
lat: apartment.lat,
lng: apartment.lng
}}
key={index}
>
<MapPoint
image={apartment.image}
address={apartment.address}
area={apartment.area}
rooms_number={apartment.rooms_number}
floor={apartment.floor}
floor_count={apartment.floor_count}
rent={apartment.rent}
/>
</OverlayContainer>
))}
</div>;
}
export default Map
アパートの模擬データの例(アパートメント).const Apartments = [
{
"id": 1,
"image": "https://storage.yandexcloud.net/apartment-images/2.jpg",
"area": 34.9,
"kitchen_area": null,
"address": "Novoalekseevskaya 4d4",
"lat": 55.80562399999999,
"lng": 37.641239,
"rooms_number": 1,
"bedrooms_number": 1,
"restrooms_number": 1,
"floor": 3,
"floor_count": 14,
"rent": 1500
},
{
"id": 2,
"image": "https://storage.yandexcloud.net/apartment-images/10_S939Rcf.jpg",
"area": 47,
"kitchen_area": null,
"address": "Valovaya street 31",
"lat": 55.66497999999999,
"lng": 37.857464,
"rooms_number": 1,
"bedrooms_number": 1,
"restrooms_number": 1,
"floor": 6,
"floor_count": 9,
"rent": 2000
},
{
"id": 3,
"image": "https://storage.yandexcloud.net/apartment-images/07_uvV7gIk.jpg",
"area": 40.9,
"kitchen_area": null,
"address": "academic Volgyn street 8A",
"lat": 55.68271799999999,
"lng": 37.544263,
"rooms_number": 3,
"bedrooms_number": 2,
"restrooms_number": 1,
"floor": 2,
"floor_count": 5,
"rent": 3000
}
]
export default Apartments
https://developers.google.com/maps/documentation/javascript/custom-markers結果
そして、我々のアプリケーションは以下の通りです.
https://material-ui.com/ru/components/cards/
ピーエス
これは、最初の記事では、私はどのようにGoogleマップとどのように動作し、さらに記事では、より多くのロジックとスタイルを可能な限り近くのBirbnb
Reference
この問題について(Airbnbのようにマップを作成する方法とGoogleマップ), 我々は、より多くの情報をここで見つけました https://dev.to/alex1998dmit/how-to-create-map-like-in-airbnb-with-react-and-google-maps-28i3テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol