简体   繁体   中英

How to change the opacity of a GroundOverlay in react-google-maps?

Using the react-google-maps example with the GroundOverlay component as an example (cf. https://github.com/khpeek/beomaps/blob/master/src/App.js ), I'd like to make a slider which changes the opacity of the overlay.

For example, currently, if I render this component, I get an overlay with a fixed opacity of 0.5 :

/* global google */

import React from "react"
import { compose } from "recompose"
import { 
  withScriptjs,
  withGoogleMap,
  GoogleMap,
  GroundOverlay } from "react-google-maps"

export const MapWithGroundOverlay = compose(
  withScriptjs,
  withGoogleMap
)(props =>
  <GoogleMap
    defaultZoom={12}
    defaultCenter={{lat: 40.740, lng: -74.18}}
  >
    <GroundOverlay
      defaultUrl="https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg"
      defaultBounds={new google.maps.LatLngBounds(
        new google.maps.LatLng(40.712216, -74.22655),
        new google.maps.LatLng(40.773941, -74.12544)
      )}
      defaultOpacity={.5}
    />
  </GoogleMap>
);

which looks like this:

在此处输入图片说明

I'm struggling to see, however, how to change the opacity 'dynamically' with a slider. Ultimately, I believe I would have to call the setOpacity method on the instance of the google.maps.GroundOverlay class (cf. https://developers.google.com/maps/documentation/javascript/reference/image-overlay#GroundOverlay ). However, if I look at the source code for the <GroundOverlay> component (cf. https://github.com/tomchentw/react-google-maps/blob/ca5c5c6f5346fb3ee0037893fbca488a408c9938/lib/components/GroundOverlay.js#L59 ),

var GroundOverlay = (exports.GroundOverlay = (function(_React$PureComponent) {
  ;(0, _inherits3.default)(GroundOverlay, _React$PureComponent)

  /*
   * @see https://developers.google.com/maps/documentation/javascript/3.exp/reference#GroundOverlay
   */
  function GroundOverlay(props, context) {
    ;(0, _classCallCheck3.default)(this, GroundOverlay)

    var _this = (0, _possibleConstructorReturn3.default)(
      this,
      (
        GroundOverlay.__proto__ || (0, _getPrototypeOf2.default)(GroundOverlay)
      ).call(this, props, context)
    )

    ;(0, _warning2.default)(
      !props.url || !props.bounds,
      "\nFor GroundOveray, url and bounds are passed in to constructor and are immutable\n after iinstantiated. This is the behavior of Google Maps JavaScript API v3 (\n See https://developers.google.com/maps/documentation/javascript/reference#GroundOverlay)\n Hence, use the corresponding two props provided by `react-google-maps`.\n They're prefixed with _default_ (defaultUrl, defaultBounds).\n\n In some cases, you'll need the GroundOverlay component to reflect the changes\n of url and bounds. You can leverage the React's key property to remount the\n component. Typically, just `key={url}` would serve your need.\n See https://github.com/tomchentw/react-google-maps/issues/655\n"
    )
    var groundOverlay = new google.maps.GroundOverlay(
      props.defaultUrl || props.url,
      props.defaultBounds || props.bounds
    )
    ;(0, _MapChildHelper.construct)(
      GroundOverlay.propTypes,
      updaterMap,
      _this.props,
      groundOverlay
    )
    groundOverlay.setMap(_this.context[_constants.MAP])
    _this.state = (0, _defineProperty3.default)(
      {},
      _constants.GROUND_LAYER,
      groundOverlay
    )
    return _this
  }

  ;(0, _createClass3.default)(GroundOverlay, [
    {
      key: "componentDidMount",
      value: function componentDidMount() {
        ;(0, _MapChildHelper.componentDidMount)(
          this,
          this.state[_constants.GROUND_LAYER],
          eventMap
        )
      },
    },
    {
      key: "componentDidUpdate",
      value: function componentDidUpdate(prevProps) {
        ;(0, _MapChildHelper.componentDidUpdate)(
          this,
          this.state[_constants.GROUND_LAYER],
          eventMap,
          updaterMap,
          prevProps
        )
      },
    },
    {
      key: "componentWillUnmount",
      value: function componentWillUnmount() {
        ;(0, _MapChildHelper.componentWillUnmount)(this)
        var GroundOverlay = this.state[_constants.GROUND_LAYER]
        if (GroundOverlay) {
          GroundOverlay.setMap(null)
        }
      },
    },
    {
      key: "render",
      value: function render() {
        return false
      },

      /**
       * Gets the `LatLngBounds` of this overlay.
       * @type LatLngBoundsLatLngBounds
       * @public
       */
    },
    {
      key: "getBounds",
      value: function getBounds() {
        return this.state[_constants.GROUND_LAYER].getBounds()
      },

      /**
       * Returns the opacity of this ground overlay.
       * @type number
       * @public
       */
    },
    {
      key: "getOpacity",
      value: function getOpacity() {
        return this.state[_constants.GROUND_LAYER].getOpacity()
      },

      /**
       * Gets the url of the projected image.
       * @type string
       * @public
       */
    },
    {
      key: "getUrl",
      value: function getUrl() {
        return this.state[_constants.GROUND_LAYER].getUrl()
      },
    },
  ])
  return GroundOverlay
})(
  _react2.default.PureComponent
)) /*

then it seems like it has only a getOpacity() method, but no setOpacity() one. Also, the groundOverlay variable is a local variable of the function GroundOverlay(props, context) , so I don't see how I could access its setOpacity method.

Any ideas on how to approach this?

Update

I've noticed that the GroundOverlay component accepts an opacity prop which determines its opacity. Using this, I tried to create a component AdjustableGroundoverlay containing both a <GroundOverlay> and a <button> (for now) to change the GroundOverlay 's opacity:

import React from "react"
import { MapWithGroundOverlay } from "./MapWithGroundOverlay"

export class AdjustableGroundoverlay extends React.PureComponent {
  constructor(props, context) {
    super(props, context)
    this.state = {opacity: 0.5}
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      opacity: 1.0
    }));
  }

  render() {
    return (
      <div>
        <MapWithGroundOverlay
          googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&v=3.exp&libraries=geometry,drawing,places`}
          loadingElement={<div style={{ height: `100%` }} />}
          containerElement={<div style={{ height: `600px` }} />}
          mapElement={<div style={{ height: `100%` }} />}
          opacity={this.state.opacity}
        />
        <button onClick={this.handleClick}>
          {`Opacity: ${this.state.opacity}`}
        </button>
      </div>
    );
  }
}

However, I've noticed that when I click the button, although its label changes from Opacity: 0.5 to Opacity: 1 , the actual opacity of the overlay doesn't change:

在此处输入图片说明

As I understand from https://reactjs.org/docs/handling-events.html , calling setState() on the AdjustableGroundoverlay should cause it to call its render() method again, thereby passing the new this.state.opacity as a prop to GroundOverlay and changing its opacity. I don't understand why this is not working?

In react-google-maps library default properties (eg defaultOpacity )are immutable after the component is instantiated, so in order GroundOverlay component to reflect the changes, opacity needs to be utilized instead.

Now comes the turn of changes, instead of accessing underlying google.maps.GroundOverlay object , i would suggest a React way via state . Since in your example recompose library is utilized opacity state could be introduced and a handler to change its value could look like this:

withStateHandlers(
    () => ({
      opacity: 0.1
    }),
    {
      incrementOpacity: ({ opacity }) => () => ({
        opacity: opacity >= 1.0 ? 0.0 : opacity + 0.1
      })
    }
)

For details about withStateHandlers refer official documentation

Here is an example to reflect the change of opacity 'dynamically' ( for demonstration purposes button is used instead of slider ):

export const MapWithGroundOverlay = compose(
  withScriptjs,
  withGoogleMap,
  withStateHandlers(
    () => ({
      opacity: 0.1
    }),
    {
      incrementOpacity: ({ opacity }) => () => ({
        opacity: opacity >= 1.0 ? 0.0 : opacity + 0.1
      })
    }
  )
)(props => (
  <div>
    <button onClick={props.incrementOpacity}>Increment opacity</button>
    <GoogleMap defaultZoom={12} defaultCenter={{ lat: 40.74, lng: -74.18 }}>
      <GroundOverlay
        defaultUrl="https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg"
        defaultBounds={
          new google.maps.LatLngBounds(
            new google.maps.LatLng(40.712216, -74.22655),
            new google.maps.LatLng(40.773941, -74.12544)
          )
        }
        defaultOpacity={props.opacity}
      />
    </GoogleMap>
  </div>
));

Here is a demo

Instead of using withStateHandlers as in the answer above, I ended up just changing the defaultOpacity prop to opacity in the MapWithGroundOverlay component:

/* global google */

import React from "react"
import { compose } from "recompose"
import {
  withScriptjs,
  withGoogleMap,
  GoogleMap,
  GroundOverlay } from "react-google-maps"

export const MapWithGroundOverlay = compose(
  withScriptjs,
  withGoogleMap
)(props =>
  <GoogleMap
    defaultZoom={12}
    defaultCenter={{lat: 40.740, lng: -74.18}}
  >
    <GroundOverlay
      defaultUrl="https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg"
      defaultBounds={new google.maps.LatLngBounds(
        new google.maps.LatLng(40.712216, -74.22655),
        new google.maps.LatLng(40.773941, -74.12544)
      )}
      opacity={props.opacity}
    />
  </GoogleMap>
);

Now, using the AjustableGroundoverlay component posted in the update above, the opacity changes when the button is clicked:

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM