I had this same issue. Here's what I finally got working to solve several of the issues I'd had with other solutions.
*Properly enforces bounds regardless of whether you use the mouse or arrow keys
*Doesn't stop short of the edge if hold down the arrow keys, due to the pan acceleration causing it to "overshoot" the edge in a single step, so it stops short instead (try holding the arrow key in one direction until you hit the edge, then release and press it again, and with some solutions, it will scroll just a little more)
*Doesn't "bounce back" when it hits the edge
*Properly enforces bounds on zoom change
EDIT: Ok, so it works when you change zoom with the scroll wheel, but not with the zoom control. Let me play around with it a bit, and I'll see if I can get that working too...
EDIT 2: Turns out, the issue was because I removed the pan control. As long as the pan control is present, this works fine, both with the scroll wheel and the zoom control.
EDIT 3: Nope... that wasn't it. I've updated the code to handle the zoom control.
// bounds of the desired area
var allowedBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-64, -64),
new google.maps.LatLng(64, 64)
);
var zoomChanged = false;
google.maps.event.addListener(map, 'center_changed', function() {
var mapBounds = map.getBounds();
if(mapBounds.getNorthEast().lat() > allowedBounds.getNorthEast().lat()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat() -
(mapBounds.getNorthEast().lat() -
allowedBounds.getNorthEast().lat()),
map.getCenter().lng(), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getNorthEast().lng() > allowedBounds.getNorthEast().lng()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat(),
map.getCenter().lng() -
(mapBounds.getNorthEast().lng() -
allowedBounds.getNorthEast().lng()), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getSouthWest().lat() < allowedBounds.getSouthWest().lat()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat() +
(allowedBounds.getSouthWest().lat() -
mapBounds.getSouthWest().lat()),
map.getCenter().lng(), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getSouthWest().lng() < allowedBounds.getSouthWest().lng()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat(),
map.getCenter().lng() +
(allowedBounds.getSouthWest().lng() -
mapBounds.getSouthWest().lng()), true);
map.panTo(newCenter);
return;
}
}, this);
google.maps.event.addListener(map, 'zoom_changed', function() {
zoomChanged = true;
}, this);
google.maps.event.addListener(map, 'bounds_changed', function() {
if(zoomChanged) {
var mapBounds = map.getBounds();
if(mapBounds.getNorthEast().lat() > allowedBounds.getNorthEast().lat()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat() -
(mapBounds.getNorthEast().lat() -
allowedBounds.getNorthEast().lat()),
map.getCenter().lng(), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getNorthEast().lng() > allowedBounds.getNorthEast().lng()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat(),
map.getCenter().lng() -
(mapBounds.getNorthEast().lng() -
allowedBounds.getNorthEast().lng()), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getSouthWest().lat() < allowedBounds.getSouthWest().lat()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat() +
(allowedBounds.getSouthWest().lat() -
mapBounds.getSouthWest().lat()),
map.getCenter().lng(), true);
map.panTo(newCenter);
return;
}
if(mapBounds.getSouthWest().lng() < allowedBounds.getSouthWest().lng()) {
var newCenter = new google.maps.LatLng(map.getCenter().lat(),
map.getCenter().lng() +
(allowedBounds.getSouthWest().lng() -
mapBounds.getSouthWest().lng()), true);
map.panTo(newCenter);
return;
}
zoomChanged = false;
}
}, this);