diff --git a/src/ol/View.js b/src/ol/View.js index 44a8570620..380196154d 100644 --- a/src/ol/View.js +++ b/src/ol/View.js @@ -635,10 +635,10 @@ class View extends BaseObject { */ calculateCenterRotate(rotation, anchor) { let center; - const currentCenter = this.targetCenter_; + const currentCenter = this.getCenter(); if (currentCenter !== undefined) { center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]]; - rotateCoordinate(center, rotation - this.targetRotation_); + rotateCoordinate(center, rotation - this.getRotation()); addCoordinate(center, anchor); } return center; @@ -651,8 +651,8 @@ class View extends BaseObject { */ calculateCenterZoom(resolution, anchor) { let center; - const currentCenter = this.targetCenter_; - const currentResolution = this.targetResolution_; + const currentCenter = this.getCenter(); + const currentResolution = this.getResolution(); if (currentCenter !== undefined && currentResolution !== undefined) { const x = anchor[0] - resolution * (anchor[0] - currentCenter[0]) / currentResolution; const y = anchor[1] - resolution * (anchor[1] - currentCenter[1]) / currentResolution; @@ -1123,17 +1123,50 @@ class View extends BaseObject { } /** - * Rotate the view around a given coordinate. - * @param {number} rotation New rotation value for the view. - * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center. + * Multiply the view resolution by a ratio, optionally using an anchor. + * @param {number} ratio The ratio to apply on the view resolution. + * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. + * @observable * @api */ - rotate(rotation, opt_anchor) { + adjustResolution(ratio, opt_anchor) { + const isMoving = this.getAnimating() || this.getInteracting(); + const size = this.getSizeFromViewport_(this.getRotation()); + const newResolution = this.constraints_.resolution(this.targetResolution_ * ratio, 0, size, isMoving); + if (opt_anchor !== undefined) { - const center = this.calculateCenterRotate(rotation, opt_anchor); - this.setCenter(center); + this.targetCenter_ = this.calculateCenterZoom(newResolution, opt_anchor); } - this.setRotation(rotation); + + this.targetResolution_ *= ratio; + this.applyParameters_(); + } + + /** + * Adds a value to the view zoom level, optionally using an anchor. + * @param {number} delta Relative value to add to the zoom level. + * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. + * @api + */ + adjustZoom(delta, opt_anchor) { + this.adjustResolution(Math.pow(this.zoomFactor_, -delta), opt_anchor); + } + + /** + * Adds a value to the view rotation, optionally using an anchor. + * @param {number} delta Relative value to add to the zoom rotation, in radians. + * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center. + * @observable + * @api + */ + adjustRotation(delta, opt_anchor) { + const isMoving = this.getAnimating() || this.getInteracting(); + const newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving); + if (opt_anchor !== undefined) { + this.targetCenter_ = this.calculateCenterRotate(newRotation, opt_anchor); + } + this.targetRotation_ += delta; + this.applyParameters_(); } /** @@ -1206,9 +1239,15 @@ class View extends BaseObject { const newResolution = this.constraints_.resolution(this.targetResolution_, 0, size, isMoving); const newCenter = this.constraints_.center(this.targetCenter_, newResolution, size, isMoving); - this.set(ViewProperty.ROTATION, newRotation); - this.set(ViewProperty.RESOLUTION, newResolution); - this.set(ViewProperty.CENTER, newCenter); + if (this.get(ViewProperty.ROTATION) !== newRotation) { + this.set(ViewProperty.ROTATION, newRotation); + } + if (this.get(ViewProperty.RESOLUTION) !== newResolution) { + this.set(ViewProperty.RESOLUTION, newResolution); + } + if (!this.get(ViewProperty.CENTER) || !equals(this.get(ViewProperty.CENTER), newCenter)) { + this.set(ViewProperty.CENTER, newCenter); + } if (this.getAnimating() && !opt_doNotCancelAnims) { this.cancelAnimations(); diff --git a/src/ol/interaction/DragRotate.js b/src/ol/interaction/DragRotate.js index 7d4d9a4be2..c7c57fead9 100644 --- a/src/ol/interaction/DragRotate.js +++ b/src/ol/interaction/DragRotate.js @@ -80,8 +80,7 @@ class DragRotate extends PointerInteraction { Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); if (this.lastAngle_ !== undefined) { const delta = theta - this.lastAngle_; - const rotation = view.getRotation(); - rotate(view, rotation - delta); + view.adjustRotation(-delta); } this.lastAngle_ = theta; } diff --git a/src/ol/interaction/DragRotateAndZoom.js b/src/ol/interaction/DragRotateAndZoom.js index 3f3d4d62c3..ab229393fe 100644 --- a/src/ol/interaction/DragRotateAndZoom.js +++ b/src/ol/interaction/DragRotateAndZoom.js @@ -4,7 +4,6 @@ import {disable} from '../rotationconstraint.js'; import ViewHint from '../ViewHint.js'; import {shiftKeyOnly, mouseOnly} from '../events/condition.js'; -import {rotate, zoom} from './Interaction.js'; import PointerInteraction from './Pointer.js'; @@ -88,14 +87,13 @@ class DragRotateAndZoom extends PointerInteraction { const theta = Math.atan2(deltaY, deltaX); const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY); const view = map.getView(); - if (view.getConstraints().rotation !== disable && this.lastAngle_ !== undefined) { - const angleDelta = theta - this.lastAngle_; - rotate(view, view.getRotation() - angleDelta); + if (this.lastAngle_ !== undefined) { + const angleDelta = this.lastAngle_ - theta; + view.adjustRotation(angleDelta); } this.lastAngle_ = theta; if (this.lastMagnitude_ !== undefined) { - const resolution = this.lastMagnitude_ * (view.getResolution() / magnitude); - zoom(view, resolution); + view.adjustResolution(this.lastMagnitude_ / magnitude); } if (this.lastMagnitude_ !== undefined) { this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; diff --git a/src/ol/interaction/Interaction.js b/src/ol/interaction/Interaction.js index b2897c8658..c48921d7ca 100644 --- a/src/ol/interaction/Interaction.js +++ b/src/ol/interaction/Interaction.js @@ -111,80 +111,14 @@ export function pan(view, delta, opt_duration) { const currentCenter = view.getCenter(); if (currentCenter) { const center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]; - if (opt_duration) { - view.animate({ - duration: opt_duration, - easing: linear, - center: center - }); - } else { - view.setCenter(center); - } + view.animate({ + duration: opt_duration !== undefined ? opt_duration : 250, + easing: linear, + center: center + }); } } - -/** - * @param {import("../View.js").default} view View. - * @param {number|undefined} rotation Rotation. - * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. - */ -export function rotate(view, rotation, opt_anchor, opt_duration) { - if (rotation !== undefined) { - const currentRotation = view.getRotation(); - const currentCenter = view.getCenter(); - if (currentRotation !== undefined && currentCenter && opt_duration > 0) { - view.animate({ - rotation: rotation, - anchor: opt_anchor, - duration: opt_duration, - easing: easeOut - }); - } else { - view.rotate(rotation, opt_anchor); - } - } -} - - -/** - * @param {import("../View.js").default} view View. - * @param {number|undefined} resolution Resolution to go to. - * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate. - * @param {number=} opt_duration Duration. - * @param {number=} opt_direction Zooming direction; > 0 indicates - * zooming out, in which case the constraints system will select - * the largest nearest resolution; < 0 indicates zooming in, in - * which case the constraints system will select the smallest - * nearest resolution; == 0 indicates that the zooming direction - * is unknown/not relevant, in which case the constraints system - * will select the nearest resolution. If not defined 0 is - * assumed. - */ -export function zoom(view, resolution, opt_anchor, opt_duration, opt_direction) { - if (resolution) { - const currentResolution = view.getResolution(); - const currentCenter = view.getCenter(); - if (currentResolution !== undefined && currentCenter && - resolution !== currentResolution && opt_duration) { - view.animate({ - resolution: resolution, - anchor: opt_anchor, - duration: opt_duration, - easing: easeOut - }); - } else { - if (opt_anchor) { - const center = view.calculateCenterZoom(resolution, opt_anchor); - view.setCenter(center); - } - view.setResolution(resolution); - } - } -} - - /** * @param {import("../View.js").default} view View. * @param {number} delta Delta from previous zoom level. @@ -201,23 +135,15 @@ export function zoomByDelta(view, delta, opt_anchor, opt_duration) { const newZoom = view.getValidZoomLevel(currentZoom + delta); const newResolution = view.getResolutionForZoom(newZoom); - if (opt_duration > 0) { - if (view.getAnimating()) { - view.cancelAnimations(); - } - view.animate({ - resolution: newResolution, - anchor: opt_anchor, - duration: opt_duration, - easing: easeOut - }); - } else { - if (opt_anchor) { - const center = view.calculateCenterZoom(newResolution, opt_anchor); - view.setCenter(center); - } - view.setResolution(newResolution); + if (view.getAnimating()) { + view.cancelAnimations(); } + view.animate({ + resolution: newResolution, + anchor: opt_anchor, + duration: opt_duration !== undefined ? opt_duration : 250, + easing: easeOut + }); } export default Interaction; diff --git a/src/ol/interaction/MouseWheelZoom.js b/src/ol/interaction/MouseWheelZoom.js index 8ba3c68fd0..47c638464f 100644 --- a/src/ol/interaction/MouseWheelZoom.js +++ b/src/ol/interaction/MouseWheelZoom.js @@ -212,49 +212,7 @@ class MouseWheelZoom extends Interaction { view.beginInteraction(); } this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_); - let resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_); - const minResolution = view.getMinResolution(); - const maxResolution = view.getMaxResolution(); - let rebound = 0; - if (resolution < minResolution) { - resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_); - rebound = 1; - } else if (resolution > maxResolution) { - resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_); - rebound = -1; - } - if (this.lastAnchor_) { - const center = view.calculateCenterZoom(resolution, this.lastAnchor_); - view.setCenter(center); - } - view.setResolution(resolution); - - if (rebound === 0) { - const zoomDelta = delta > 0 ? -1 : 1; - const newZoom = view.getValidZoomLevel(view.getZoom() + zoomDelta); - view.animate({ - resolution: view.getResolutionForZoom(newZoom), - easing: easeOut, - anchor: this.lastAnchor_, - duration: this.duration_ - }); - } - - if (rebound > 0) { - view.animate({ - resolution: minResolution, - easing: easeOut, - anchor: this.lastAnchor_, - duration: 500 - }); - } else if (rebound < 0) { - view.animate({ - resolution: maxResolution, - easing: easeOut, - anchor: this.lastAnchor_, - duration: 500 - }); - } + view.adjustZoom(-delta / this.trackpadDeltaPerZoom_, this.lastAnchor_); this.startTime_ = now; return false; } diff --git a/src/ol/interaction/PinchRotate.js b/src/ol/interaction/PinchRotate.js index fba8c0ff96..f18652557a 100644 --- a/src/ol/interaction/PinchRotate.js +++ b/src/ol/interaction/PinchRotate.js @@ -118,9 +118,8 @@ class PinchRotate extends PointerInteraction { // rotate if (this.rotating_) { - const rotation = view.getRotation(); map.render(); - rotate(view, rotation + rotationDelta, this.anchor_); + view.adjustRotation(rotationDelta, this.anchor_); } } diff --git a/src/ol/interaction/PinchZoom.js b/src/ol/interaction/PinchZoom.js index b3c1e16c90..d3e0e9f6bd 100644 --- a/src/ol/interaction/PinchZoom.js +++ b/src/ol/interaction/PinchZoom.js @@ -83,17 +83,6 @@ class PinchZoom extends PointerInteraction { const map = mapBrowserEvent.map; const view = map.getView(); - const resolution = view.getResolution(); - const maxResolution = view.getMaxResolution(); - const minResolution = view.getMinResolution(); - let newResolution = resolution * scaleDelta; - if (newResolution > maxResolution) { - scaleDelta = maxResolution / resolution; - newResolution = maxResolution; - } else if (newResolution < minResolution) { - scaleDelta = minResolution / resolution; - newResolution = minResolution; - } if (scaleDelta != 1.0) { this.lastScaleDelta_ = scaleDelta; @@ -108,7 +97,7 @@ class PinchZoom extends PointerInteraction { // scale, bypass the resolution constraint map.render(); - zoom(view, newResolution, this.anchor_); + view.adjustResolution(scaleDelta, this.anchor_); } /** @@ -118,7 +107,7 @@ class PinchZoom extends PointerInteraction { if (this.targetPointers.length < 2) { const map = mapBrowserEvent.map; const view = map.getView(); - const direction = this.lastScaleDelta_ - 1; + const direction = this.lastScaleDelta_ > 1 ? 1 : -1; view.endInteraction(this.duration_, direction); return false; } else { diff --git a/test/spec/ol/interaction/dragrotateandzoom.test.js b/test/spec/ol/interaction/dragrotateandzoom.test.js index 7a9249182e..47bc94cab4 100644 --- a/test/spec/ol/interaction/dragrotateandzoom.test.js +++ b/test/spec/ol/interaction/dragrotateandzoom.test.js @@ -62,13 +62,18 @@ describe('ol.interaction.DragRotateAndZoom', function() { true); interaction.lastAngle_ = Math.PI; - let view = map.getView(); - let spy = sinon.spy(view, 'rotate'); - interaction.handleDragEvent(event); - expect(spy.callCount).to.be(1); - expect(interaction.lastAngle_).to.be(-0.8308214428190254); - view.rotate.restore(); + let callCount = 0; + let view = map.getView(); + view.on('change:rotation', function() { + callCount++; + }); + + interaction.handleDragEvent(event); + expect(callCount).to.be(1); + expect(interaction.lastAngle_).to.be(-0.8308214428190254); + + callCount = 0; view = new View({ projection: 'EPSG:4326', center: [0, 0], @@ -76,15 +81,16 @@ describe('ol.interaction.DragRotateAndZoom', function() { enableRotation: false }); map.setView(view); + view.on('change:rotation', function() { + callCount++; + }); event = new MapBrowserPointerEvent('pointermove', map, new PointerEvent('pointermove', {clientX: 24, clientY: 16}, {pointerType: 'mouse'}), true); - spy = sinon.spy(view, 'rotate'); interaction.handleDragEvent(event); - expect(spy.callCount).to.be(0); - view.rotate.restore(); + expect(callCount).to.be(0); }); }); diff --git a/test/spec/ol/interaction/interaction.test.js b/test/spec/ol/interaction/interaction.test.js index 5a6f14a703..7d752ab0c9 100644 --- a/test/spec/ol/interaction/interaction.test.js +++ b/test/spec/ol/interaction/interaction.test.js @@ -87,112 +87,4 @@ describe('ol.interaction.Interaction', function() { }); - describe('zoomByDelta()', function() { - - it('changes view resolution', function() { - const view = new View({ - resolution: 1, - resolutions: [4, 2, 1, 0.5, 0.25] - }); - - zoomByDelta(view, 1); - expect(view.getResolution()).to.be(0.5); - - zoomByDelta(view, -1); - expect(view.getResolution()).to.be(1); - - zoomByDelta(view, 2); - expect(view.getResolution()).to.be(0.25); - - zoomByDelta(view, -2); - expect(view.getResolution()).to.be(1); - }); - - it('changes view resolution and center relative to the anchor', function() { - const view = new View({ - center: [0, 0], - resolution: 1, - resolutions: [4, 2, 1, 0.5, 0.25] - }); - - zoomByDelta(view, 1, [10, 10]); - expect(view.getCenter()).to.eql([5, 5]); - - zoomByDelta(view, -1, [0, 0]); - expect(view.getCenter()).to.eql([10, 10]); - - zoomByDelta(view, 2, [0, 0]); - expect(view.getCenter()).to.eql([2.5, 2.5]); - - zoomByDelta(view, -2, [0, 0]); - expect(view.getCenter()).to.eql([10, 10]); - }); - - it('changes view resolution and center relative to the anchor, while respecting the extent (center only)', function() { - const view = new View({ - center: [0, 0], - extent: [-2.5, -2.5, 2.5, 2.5], - constrainOnlyCenter: true, - resolution: 1, - resolutions: [4, 2, 1, 0.5, 0.25] - }); - - zoomByDelta(view, 1, [10, 10]); - expect(view.getCenter()).to.eql([2.5, 2.5]); - - zoomByDelta(view, -1, [0, 0]); - expect(view.getCenter()).to.eql([2.5, 2.5]); - - zoomByDelta(view, 2, [10, 10]); - expect(view.getCenter()).to.eql([2.5, 2.5]); - - zoomByDelta(view, -2, [0, 0]); - expect(view.getCenter()).to.eql([2.5, 2.5]); - }); - - it('changes view resolution and center relative to the anchor, while respecting the extent', function() { - const map = new Map({}); - const view = new View({ - center: [50, 50], - extent: [0, 0, 100, 100], - resolution: 1, - resolutions: [4, 2, 1, 0.5, 0.25, 0.125] - }); - map.setView(view); - - zoomByDelta(view, 1, [100, 100]); - expect(view.getCenter()).to.eql([75, 75]); - - zoomByDelta(view, -1, [75, 75]); - expect(view.getCenter()).to.eql([50, 50]); - - zoomByDelta(view, 2, [100, 100]); - expect(view.getCenter()).to.eql([87.5, 87.5]); - - zoomByDelta(view, -3, [0, 0]); - expect(view.getCenter()).to.eql([50, 50]); - expect(view.getResolution()).to.eql(2); - }); - - it('changes view resolution and center relative to the anchor, while respecting the extent (rotated)', function() { - const map = new Map({}); - const view = new View({ - center: [50, 50], - extent: [-100, -100, 100, 100], - resolution: 1, - resolutions: [2, 1, 0.5, 0.25, 0.125], - rotation: Math.PI / 4 - }); - map.setView(view); - const halfSize = 100 * Math.SQRT1_2; - - zoomByDelta(view, 1, [100, 100]); - expect(view.getCenter()).to.eql([100 - halfSize / 2, 100 - halfSize / 2]); - - view.setCenter([0, 50]); - zoomByDelta(view, -1, [0, 0]); - expect(view.getCenter()).to.eql([0, 100 - halfSize]); - }); - }); - }); diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js index 669a12ab7c..3b52a5ba89 100644 --- a/test/spec/ol/view.test.js +++ b/test/spec/ol/view.test.js @@ -8,6 +8,7 @@ import {createEmpty} from '../../../src/ol/extent.js'; import Circle from '../../../src/ol/geom/Circle.js'; import LineString from '../../../src/ol/geom/LineString.js'; import Point from '../../../src/ol/geom/Point.js'; +import {zoomByDelta} from '../../../src/ol/interaction/Interaction'; describe('ol.View', function() { @@ -1559,6 +1560,152 @@ describe('ol.View', function() { expect(view.getValidResolution(4, -1)).to.be(8); }); }); + + describe('#adjustRotation()', function() { + it('changes view rotation with anchor', function() { + const view = new View({ + resolution: 1, + center: [0, 0] + }); + + view.adjustRotation(Math.PI / 2); + expect(view.getRotation()).to.be(Math.PI / 2); + expect(view.getCenter()).to.eql([0, 0]); + + view.adjustRotation(-Math.PI); + expect(view.getRotation()).to.be(-Math.PI / 2); + expect(view.getCenter()).to.eql([0, 0]); + + view.adjustRotation(Math.PI / 3, [50, 0]); + expect(view.getRotation()).to.roughlyEqual(-Math.PI / 6, 1e-9); + expect(view.getCenter()[0]).to.roughlyEqual(50 * (1 - Math.cos(Math.PI / 3)), 1e-9); + expect(view.getCenter()[1]).to.roughlyEqual(-50 * Math.sin(Math.PI / 3), 1e-9); + }); + + it('does not change view parameters if rotation is disabled', function() { + const view = new View({ + resolution: 1, + enableRotation: false, + center: [0, 0] + }); + + view.adjustRotation(Math.PI / 2); + expect(view.getRotation()).to.be(0); + expect(view.getCenter()).to.eql([0, 0]); + + view.adjustRotation(-Math.PI * 3, [-50, 0]); + expect(view.getRotation()).to.be(0); + expect(view.getCenter()).to.eql([0, 0]); + }); + }); + + describe('#adjustZoom()', function() { + + it('changes view resolution', function() { + const view = new View({ + resolution: 1, + resolutions: [4, 2, 1, 0.5, 0.25] + }); + + view.adjustZoom(1); + expect(view.getResolution()).to.be(0.5); + + view.adjustZoom(-1); + expect(view.getResolution()).to.be(1); + + view.adjustZoom(2); + expect(view.getResolution()).to.be(0.25); + + view.adjustZoom(-2); + expect(view.getResolution()).to.be(1); + }); + + it('changes view resolution and center relative to the anchor', function() { + const view = new View({ + center: [0, 0], + resolution: 1, + resolutions: [4, 2, 1, 0.5, 0.25] + }); + + view.adjustZoom(1, [10, 10]); + expect(view.getCenter()).to.eql([5, 5]); + + view.adjustZoom(-1, [0, 0]); + expect(view.getCenter()).to.eql([10, 10]); + + view.adjustZoom(2, [0, 0]); + expect(view.getCenter()).to.eql([2.5, 2.5]); + + view.adjustZoom(-2, [0, 0]); + expect(view.getCenter()).to.eql([10, 10]); + }); + + it('changes view resolution and center relative to the anchor, while respecting the extent (center only)', function() { + const view = new View({ + center: [0, 0], + extent: [-2.5, -2.5, 2.5, 2.5], + constrainOnlyCenter: true, + resolution: 1, + resolutions: [4, 2, 1, 0.5, 0.25] + }); + + view.adjustZoom(1, [10, 10]); + expect(view.getCenter()).to.eql([2.5, 2.5]); + + view.adjustZoom(-1, [0, 0]); + expect(view.getCenter()).to.eql([2.5, 2.5]); + + view.adjustZoom(2, [10, 10]); + expect(view.getCenter()).to.eql([2.5, 2.5]); + + view.adjustZoom(-2, [0, 0]); + expect(view.getCenter()).to.eql([2.5, 2.5]); + }); + + it('changes view resolution and center relative to the anchor, while respecting the extent', function() { + const map = new Map({}); + const view = new View({ + center: [50, 50], + extent: [0, 0, 100, 100], + resolution: 1, + resolutions: [4, 2, 1, 0.5, 0.25, 0.125] + }); + map.setView(view); + + view.adjustZoom(1, [100, 100]); + expect(view.getCenter()).to.eql([75, 75]); + + view.adjustZoom(-1, [75, 75]); + expect(view.getCenter()).to.eql([50, 50]); + + view.adjustZoom(2, [100, 100]); + expect(view.getCenter()).to.eql([87.5, 87.5]); + + view.adjustZoom(-3, [0, 0]); + expect(view.getCenter()).to.eql([50, 50]); + expect(view.getResolution()).to.eql(1); + }); + + it('changes view resolution and center relative to the anchor, while respecting the extent (rotated)', function() { + const map = new Map({}); + const view = new View({ + center: [50, 50], + extent: [-100, -100, 100, 100], + resolution: 1, + resolutions: [2, 1, 0.5, 0.25, 0.125], + rotation: Math.PI / 4 + }); + map.setView(view); + const halfSize = 100 * Math.SQRT1_2; + + view.adjustZoom(1, [100, 100]); + expect(view.getCenter()).to.eql([100 - halfSize / 2, 100 - halfSize / 2]); + + view.setCenter([0, 50]); + view.adjustZoom(-1, [0, 0]); + expect(view.getCenter()).to.eql([0, 100 - halfSize]); + }); + }); }); describe('ol.View.isNoopAnimation()', function() {