style: Apply prettier formatting
Apply consistent code formatting with prettier to all project files
This commit is contained in:
parent
5194dde20b
commit
c0546dbf46
15 changed files with 421 additions and 440 deletions
64
CHANGELOG.md
64
CHANGELOG.md
|
|
@ -35,15 +35,15 @@ This release modernizes the library with Angular 20 and signals API.
|
|||
- **Karma/Jasmine**: Removed old testing infrastructure
|
||||
- **ESLint**: Removed eslint and unnecessary linting dependencies
|
||||
- **Polyfills**: Removed polyfills.ts (not needed in modern browsers)
|
||||
- **Test Files**: Removed *.spec.ts files (to be reimplemented with modern testing tools)
|
||||
- **Test Files**: Removed \*.spec.ts files (to be reimplemented with modern testing tools)
|
||||
- **Backward Compatibility Inputs**: Removed deprecated kebab-case input bindings
|
||||
|
||||
### 🔧 Technical Changes
|
||||
|
||||
- Updated `tsconfig.json` with modern settings:
|
||||
- `moduleResolution: "bundler"`
|
||||
- `strict: true`
|
||||
- `strictTemplates: true`
|
||||
- `moduleResolution: "bundler"`
|
||||
- `strict: true`
|
||||
- `strictTemplates: true`
|
||||
- Simplified `angular.json` configuration
|
||||
- Removed deprecated tslint configuration
|
||||
- Updated package scripts for simpler workflow
|
||||
|
|
@ -52,6 +52,7 @@ This release modernizes the library with Angular 20 and signals API.
|
|||
### 📦 Dependencies
|
||||
|
||||
#### Updated
|
||||
|
||||
- `@angular/*`: 19.0.0 → 20.0.0
|
||||
- `typescript`: 5.5.4 → 5.8.0
|
||||
- `rxjs`: 7.5.2 → 7.8.0
|
||||
|
|
@ -61,6 +62,7 @@ This release modernizes the library with Angular 20 and signals API.
|
|||
- `prettier`: 2.7.1 → 3.0.0
|
||||
|
||||
#### Removed
|
||||
|
||||
- `cypress`
|
||||
- `@cypress/schematic`
|
||||
- `karma` and related packages
|
||||
|
|
@ -91,13 +93,15 @@ This release modernizes the library with Angular 20 and signals API.
|
|||
2. **Minimum TypeScript Version**: Now requires TypeScript 5.8.0+
|
||||
3. **Minimum Node Version**: Now requires Node.js 18.19.1+
|
||||
4. **Component Properties**: Computed properties must be called as functions:
|
||||
```typescript
|
||||
// Before
|
||||
component.scale
|
||||
|
||||
// After
|
||||
component.scale()
|
||||
```
|
||||
```typescript
|
||||
// Before
|
||||
component.scale;
|
||||
|
||||
// After
|
||||
component.scale();
|
||||
```
|
||||
|
||||
5. **Removed Exports**: Some internal utilities may no longer be exported
|
||||
|
||||
### 🔄 Migration Guide
|
||||
|
|
@ -105,34 +109,38 @@ This release modernizes the library with Angular 20 and signals API.
|
|||
For users upgrading from previous versions:
|
||||
|
||||
1. Update Angular to 20+:
|
||||
```bash
|
||||
ng update @angular/core@20 @angular/cli@20
|
||||
```
|
||||
|
||||
```bash
|
||||
ng update @angular/core@20 @angular/cli@20
|
||||
```
|
||||
|
||||
2. Update your package.json:
|
||||
```bash
|
||||
npm install @meddv/ngx-pinch-zoom@20.0.0
|
||||
```
|
||||
|
||||
```bash
|
||||
npm install @meddv/ngx-pinch-zoom@20.0.0
|
||||
```
|
||||
|
||||
3. Template changes: None required! Input/output binding syntax remains the same.
|
||||
|
||||
4. Component reference changes (if using ViewChild):
|
||||
```typescript
|
||||
// Before
|
||||
@ViewChild('pinchZoom') pinchZoom: PinchZoomComponent;
|
||||
|
||||
// After (recommended)
|
||||
pinchZoom = viewChild<PinchZoomComponent>('pinchZoom');
|
||||
```
|
||||
```typescript
|
||||
// Before
|
||||
@ViewChild('pinchZoom') pinchZoom: PinchZoomComponent;
|
||||
|
||||
// After (recommended)
|
||||
pinchZoom = viewChild<PinchZoomComponent>('pinchZoom');
|
||||
```
|
||||
|
||||
5. Accessing computed properties:
|
||||
```typescript
|
||||
// Before
|
||||
const scale = this.pinchZoom.scale;
|
||||
|
||||
// After
|
||||
const scale = this.pinchZoom()?.scale();
|
||||
```
|
||||
```typescript
|
||||
// Before
|
||||
const scale = this.pinchZoom.scale;
|
||||
|
||||
// After
|
||||
const scale = this.pinchZoom()?.scale();
|
||||
```
|
||||
|
||||
### 🙏 Credits
|
||||
|
||||
|
|
|
|||
|
|
@ -40,20 +40,20 @@ ngx-pinch-zoom/
|
|||
### Core Components
|
||||
|
||||
1. **PinchZoomComponent** (`pinch-zoom.component.ts`)
|
||||
- Main Angular component
|
||||
- Uses signals for all inputs and outputs
|
||||
- Implements modern Angular patterns (inject, computed, effect)
|
||||
- Standalone component
|
||||
- Main Angular component
|
||||
- Uses signals for all inputs and outputs
|
||||
- Implements modern Angular patterns (inject, computed, effect)
|
||||
- Standalone component
|
||||
|
||||
2. **IvyPinch** (`ivypinch.ts`)
|
||||
- Core zoom/pan logic
|
||||
- Handles transformations and calculations
|
||||
- No Angular dependencies (plain TypeScript class)
|
||||
- Core zoom/pan logic
|
||||
- Handles transformations and calculations
|
||||
- No Angular dependencies (plain TypeScript class)
|
||||
|
||||
3. **Touches** (`touches.ts`)
|
||||
- Event handling for touch and mouse interactions
|
||||
- Gesture detection (pinch, pan, tap, double-tap)
|
||||
- Cross-browser compatibility
|
||||
- Event handling for touch and mouse interactions
|
||||
- Gesture detection (pinch, pan, tap, double-tap)
|
||||
- Cross-browser compatibility
|
||||
|
||||
## Development Setup
|
||||
|
||||
|
|
@ -94,6 +94,7 @@ npm run build-lib
|
|||
```
|
||||
|
||||
Output is generated in `dist/ngx-pinch-zoom/` with:
|
||||
|
||||
- ESM bundles
|
||||
- TypeScript declaration files
|
||||
- package.json for npm publishing
|
||||
|
|
@ -121,7 +122,7 @@ disabled = input<boolean>(false);
|
|||
zoomChanged = output<number>();
|
||||
|
||||
scale = computed<number>(() => {
|
||||
return this.currentScale();
|
||||
return this.currentScale();
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -147,30 +148,30 @@ The project uses strict TypeScript settings:
|
|||
|
||||
```json
|
||||
{
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
```
|
||||
|
||||
### Adding New Features
|
||||
|
||||
1. **Update the component** (`pinch-zoom.component.ts`):
|
||||
- Add input signals for configuration
|
||||
- Add computed signals for derived state
|
||||
- Update the component template if needed
|
||||
- Add input signals for configuration
|
||||
- Add computed signals for derived state
|
||||
- Update the component template if needed
|
||||
|
||||
2. **Update core logic** (`ivypinch.ts`):
|
||||
- Add properties to the `Properties` interface
|
||||
- Implement the functionality
|
||||
- Test calculations thoroughly
|
||||
- Add properties to the `Properties` interface
|
||||
- Implement the functionality
|
||||
- Test calculations thoroughly
|
||||
|
||||
3. **Document the feature**:
|
||||
- Update README.md with the new property
|
||||
- Add usage examples
|
||||
- Document default values
|
||||
- Update README.md with the new property
|
||||
- Add usage examples
|
||||
- Document default values
|
||||
|
||||
### Testing Changes
|
||||
|
||||
|
|
@ -205,16 +206,16 @@ npm update
|
|||
### Debugging
|
||||
|
||||
1. **Component Issues**:
|
||||
- Check signal reactivity with Angular DevTools
|
||||
- Use `effect(() => console.log(signalValue()))`
|
||||
- Check signal reactivity with Angular DevTools
|
||||
- Use `effect(() => console.log(signalValue()))`
|
||||
|
||||
2. **Touch/Mouse Issues**:
|
||||
- Add console.logs in `touches.ts` event handlers
|
||||
- Check browser dev tools for event listeners
|
||||
- Add console.logs in `touches.ts` event handlers
|
||||
- Check browser dev tools for event listeners
|
||||
|
||||
3. **Transform Issues**:
|
||||
- Inspect `ivypinch.ts` transformElement method
|
||||
- Check CSS transform values in browser inspector
|
||||
- Inspect `ivypinch.ts` transformElement method
|
||||
- Check CSS transform values in browser inspector
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
|
|
@ -237,12 +238,14 @@ The library supports all modern browsers:
|
|||
### Build Failures
|
||||
|
||||
**TypeScript errors**: Ensure strict mode compliance
|
||||
|
||||
```bash
|
||||
# Check TypeScript version
|
||||
npx tsc --version # Should be ~5.8.0
|
||||
```
|
||||
|
||||
**Dependency conflicts**: Clear cache and reinstall
|
||||
|
||||
```bash
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
|
|
|
|||
188
angular.json
188
angular.json
|
|
@ -1,101 +1,101 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"cli": {
|
||||
"analytics": false
|
||||
},
|
||||
"projects": {
|
||||
"ivypinchApp": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "sass"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/ivypinchApp",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.sass"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kB",
|
||||
"maximumError": "10kB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "ivypinchApp:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "ivypinchApp:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
}
|
||||
}
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"cli": {
|
||||
"analytics": false
|
||||
},
|
||||
"ngx-pinch-zoom": {
|
||||
"projectType": "library",
|
||||
"root": "projects/ngx-pinch-zoom",
|
||||
"sourceRoot": "projects/ngx-pinch-zoom/src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"project": "projects/ngx-pinch-zoom/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.prod.json"
|
||||
"projects": {
|
||||
"ivypinchApp": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "sass"
|
||||
}
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.json"
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/ivypinchApp",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.sass"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kB",
|
||||
"maximumError": "10kB"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "ivypinchApp:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "ivypinchApp:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ngx-pinch-zoom": {
|
||||
"projectType": "library",
|
||||
"root": "projects/ngx-pinch-zoom",
|
||||
"sourceRoot": "projects/ngx-pinch-zoom/src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:ng-packagr",
|
||||
"options": {
|
||||
"project": "projects/ngx-pinch-zoom/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.prod.json"
|
||||
},
|
||||
"development": {
|
||||
"tsConfig": "projects/ngx-pinch-zoom/tsconfig.lib.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,42 +77,44 @@ graph TB
|
|||
### Design Principles
|
||||
|
||||
1. **Separation of Concerns**
|
||||
- **PinchZoomComponent**: Angular integration, signals, lifecycle
|
||||
- **IvyPinch**: Pure business logic, no Angular dependencies
|
||||
- **Touches**: Pure event handling, no Angular dependencies
|
||||
- **PinchZoomComponent**: Angular integration, signals, lifecycle
|
||||
- **IvyPinch**: Pure business logic, no Angular dependencies
|
||||
- **Touches**: Pure event handling, no Angular dependencies
|
||||
|
||||
2. **Signal-Based Reactivity**
|
||||
- All inputs use Angular 20+ input signals
|
||||
- Computed signals for derived state
|
||||
- Output signals for events
|
||||
- Effects for side effects
|
||||
- All inputs use Angular 20+ input signals
|
||||
- Computed signals for derived state
|
||||
- Output signals for events
|
||||
- Effects for side effects
|
||||
|
||||
3. **Framework Independence**
|
||||
- IvyPinch and Touches can work without Angular
|
||||
- Could be ported to React, Vue, or vanilla JS
|
||||
- DOM manipulation is isolated
|
||||
- IvyPinch and Touches can work without Angular
|
||||
- Could be ported to React, Vue, or vanilla JS
|
||||
- DOM manipulation is isolated
|
||||
|
||||
4. **Performance First**
|
||||
- CSS transforms for hardware acceleration
|
||||
- `translate3d` forces GPU rendering
|
||||
- No layout thrashing (read/write separation)
|
||||
- Minimal change detection triggers
|
||||
- CSS transforms for hardware acceleration
|
||||
- `translate3d` forces GPU rendering
|
||||
- No layout thrashing (read/write separation)
|
||||
- Minimal change detection triggers
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### PinchZoomComponent (Angular Layer)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Provide Angular component interface
|
||||
- Manage input/output signals
|
||||
- Initialize and cleanup IvyPinch
|
||||
- Bridge between Angular and core logic
|
||||
|
||||
**Key Features:**
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'pinch-zoom, [pinch-zoom]',
|
||||
standalone: true, // No NgModule needed
|
||||
standalone: true, // No NgModule needed
|
||||
// ...
|
||||
})
|
||||
export class PinchZoomComponent {
|
||||
|
|
@ -143,6 +145,7 @@ export class PinchZoomComponent {
|
|||
### IvyPinch (Core Logic Layer)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Calculate zoom scale from pinch gestures
|
||||
- Calculate pan position from swipe gestures
|
||||
- Apply constraints (min/max zoom, pan limits)
|
||||
|
|
@ -150,21 +153,22 @@ export class PinchZoomComponent {
|
|||
- Notify component of changes
|
||||
|
||||
**Key State:**
|
||||
|
||||
```typescript
|
||||
class IvyPinch {
|
||||
// Zoom state
|
||||
public scale: number = 1; // Current scale (1 = 100%)
|
||||
private initialScale: number = 1; // Scale at gesture start
|
||||
private maxScale: number = 3; // Maximum allowed scale
|
||||
public scale: number = 1; // Current scale (1 = 100%)
|
||||
private initialScale: number = 1; // Scale at gesture start
|
||||
private maxScale: number = 3; // Maximum allowed scale
|
||||
|
||||
// Position state
|
||||
public moveX: number = 0; // Horizontal offset (px)
|
||||
public moveY: number = 0; // Vertical offset (px)
|
||||
private initialMoveX: number = 0; // Position at gesture start
|
||||
public moveX: number = 0; // Horizontal offset (px)
|
||||
public moveY: number = 0; // Vertical offset (px)
|
||||
private initialMoveX: number = 0; // Position at gesture start
|
||||
private initialMoveY: number = 0;
|
||||
|
||||
// Gesture state
|
||||
private distance: number = 0; // Distance between fingers
|
||||
private distance: number = 0; // Distance between fingers
|
||||
private initialDistance: number = 0; // Distance at pinch start
|
||||
}
|
||||
```
|
||||
|
|
@ -172,12 +176,14 @@ class IvyPinch {
|
|||
### Touches (Event Handling Layer)
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Listen to touch/mouse events
|
||||
- Detect gesture types (pinch, pan, double-tap)
|
||||
- Calculate touch positions and distances
|
||||
- Emit typed events to IvyPinch
|
||||
|
||||
**Event Flow:**
|
||||
|
||||
```typescript
|
||||
class Touches {
|
||||
// Event type state machine
|
||||
|
|
@ -190,14 +196,14 @@ class Touches {
|
|||
private handlers = {
|
||||
pinch: [],
|
||||
pan: [],
|
||||
tap: []
|
||||
tap: [],
|
||||
};
|
||||
|
||||
// Core detection loop
|
||||
detectGesture() {
|
||||
this.detectDoubleTap() || // Check tap first
|
||||
this.detectPinch() || // Then pinch
|
||||
this.detectLinearSwipe(); // Finally pan
|
||||
this.detectDoubleTap() || // Check tap first
|
||||
this.detectPinch() || // Then pinch
|
||||
this.detectLinearSwipe(); // Finally pan
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -281,6 +287,7 @@ flowchart TD
|
|||
### Why Signals?
|
||||
|
||||
Angular 20's signals provide:
|
||||
|
||||
1. **Fine-grained reactivity** - Only affected components update
|
||||
2. **Automatic dependency tracking** - Computed signals track dependencies
|
||||
3. **Better performance** - Less change detection overhead
|
||||
|
|
@ -393,6 +400,7 @@ mergedProperties = computed<ComponentProperties>(() => {
|
|||
```
|
||||
|
||||
This ensures:
|
||||
|
||||
- Individual inputs take priority
|
||||
- properties object can set multiple at once
|
||||
- Defaults are always present
|
||||
|
|
@ -609,18 +617,18 @@ emit(eventName: string, event: any) {
|
|||
|
||||
```typescript
|
||||
type TouchEvent =
|
||||
| 'touchstart' // Finger touches screen
|
||||
| 'touchmove' // Finger moves
|
||||
| 'touchend' // Finger lifts
|
||||
| 'mousedown' // Mouse button pressed
|
||||
| 'mousemove' // Mouse moves
|
||||
| 'mouseup' // Mouse button released
|
||||
| 'wheel'; // Mouse wheel scrolled
|
||||
| 'touchstart' // Finger touches screen
|
||||
| 'touchmove' // Finger moves
|
||||
| 'touchend' // Finger lifts
|
||||
| 'mousedown' // Mouse button pressed
|
||||
| 'mousemove' // Mouse moves
|
||||
| 'mouseup' // Mouse button released
|
||||
| 'wheel'; // Mouse wheel scrolled
|
||||
|
||||
type GestureEvent =
|
||||
| 'pinch' // Two-finger zoom
|
||||
| 'pan' // One-finger drag
|
||||
| 'tap'; // Double-tap
|
||||
| 'pinch' // Two-finger zoom
|
||||
| 'pan' // One-finger drag
|
||||
| 'tap'; // Double-tap
|
||||
```
|
||||
|
||||
## Transform Mathematics
|
||||
|
|
@ -668,6 +676,7 @@ const newMoveY = this.moveY - (centerY - this.moveY) * (newScale / this.scale -
|
|||
#### Mathematical Derivation
|
||||
|
||||
Let:
|
||||
|
||||
- `P` = point to zoom at (in screen coordinates)
|
||||
- `S₁` = old scale
|
||||
- `S₂` = new scale
|
||||
|
|
@ -677,7 +686,7 @@ Let:
|
|||
We want: Point P maps to same position before and after zoom.
|
||||
|
||||
Before zoom: `P_image = (P - T₁) / S₁`
|
||||
After zoom: `P_image = (P - T₂) / S₂`
|
||||
After zoom: `P_image = (P - T₂) / S₂`
|
||||
|
||||
Setting equal:
|
||||
`(P - T₁) / S₁ = (P - T₂) / S₂`
|
||||
|
|
@ -699,10 +708,12 @@ transform: translate(100px, 50px) scale(2);
|
|||
```
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
1. Scale by 2
|
||||
2. Then translate by (100px, 50px)
|
||||
|
||||
**Our transform:**
|
||||
|
||||
```css
|
||||
transform: translate3d(moveX, moveY, 0) scale(scale);
|
||||
```
|
||||
|
|
@ -721,6 +732,7 @@ transform: translate3d(100px, 50px, 0) scale(2);
|
|||
```
|
||||
|
||||
Becomes:
|
||||
|
||||
```
|
||||
| 2 0 0 100 |
|
||||
| 0 2 0 50 |
|
||||
|
|
|
|||
|
|
@ -20,15 +20,17 @@ Let's add the ability to rotate images with a two-finger twist gesture.
|
|||
#### Step 1: Add Properties
|
||||
|
||||
**File: `interfaces.ts`**
|
||||
|
||||
```typescript
|
||||
export interface Properties {
|
||||
// ... existing properties
|
||||
enableRotation?: boolean;
|
||||
rotationLockThreshold?: number; // Degrees before rotation activates
|
||||
rotationLockThreshold?: number; // Degrees before rotation activates
|
||||
}
|
||||
```
|
||||
|
||||
**File: `properties.ts`**
|
||||
|
||||
```typescript
|
||||
export const defaultProperties: Properties = {
|
||||
// ... existing defaults
|
||||
|
|
@ -40,6 +42,7 @@ export const defaultProperties: Properties = {
|
|||
#### Step 2: Add Input Signal
|
||||
|
||||
**File: `pinch-zoom.component.ts`**
|
||||
|
||||
```typescript
|
||||
export class PinchZoomComponent implements OnInit, OnDestroy {
|
||||
// ... existing inputs
|
||||
|
|
@ -60,14 +63,15 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
#### Step 3: Add State to IvyPinch
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
export class IvyPinch {
|
||||
// ... existing properties
|
||||
|
||||
// Rotation state
|
||||
public rotation: number = 0; // Current rotation in degrees
|
||||
private initialRotation: number = 0; // Rotation at gesture start
|
||||
private rotationAngle: number = 0; // Cumulative rotation
|
||||
public rotation: number = 0; // Current rotation in degrees
|
||||
private initialRotation: number = 0; // Rotation at gesture start
|
||||
private rotationAngle: number = 0; // Cumulative rotation
|
||||
|
||||
// ... existing methods
|
||||
|
||||
|
|
@ -86,10 +90,7 @@ export class IvyPinch {
|
|||
const touch2 = touches[1];
|
||||
|
||||
// Calculate angle between fingers
|
||||
const angle = Math.atan2(
|
||||
touch2.clientY - touch1.clientY,
|
||||
touch2.clientX - touch1.clientX
|
||||
) * (180 / Math.PI);
|
||||
const angle = Math.atan2(touch2.clientY - touch1.clientY, touch2.clientX - touch1.clientX) * (180 / Math.PI);
|
||||
|
||||
// Initialize on first move
|
||||
if (this.rotationAngle === 0) {
|
||||
|
|
@ -114,20 +115,10 @@ export class IvyPinch {
|
|||
this.rotation = this.initialRotation + delta;
|
||||
|
||||
// Apply transform with rotation
|
||||
this.transformElementWithRotation(
|
||||
this.scale,
|
||||
this.moveX,
|
||||
this.moveY,
|
||||
this.rotation
|
||||
);
|
||||
this.transformElementWithRotation(this.scale, this.moveX, this.moveY, this.rotation);
|
||||
};
|
||||
|
||||
private transformElementWithRotation(
|
||||
scale: number,
|
||||
moveX: number,
|
||||
moveY: number,
|
||||
rotation: number
|
||||
): void {
|
||||
private transformElementWithRotation(scale: number, moveX: number, moveY: number, rotation: number): void {
|
||||
const transform = `
|
||||
translate3d(${moveX}px, ${moveY}px, 0)
|
||||
scale(${scale})
|
||||
|
|
@ -146,6 +137,7 @@ export class IvyPinch {
|
|||
#### Step 4: Register Event Handler
|
||||
|
||||
**File: `ivypinch.ts` (in constructor)**
|
||||
|
||||
```typescript
|
||||
constructor(properties: Properties, private zoomChanged: (scale: number) => void) {
|
||||
// ... existing initialization
|
||||
|
|
@ -166,6 +158,7 @@ constructor(properties: Properties, private zoomChanged: (scale: number) => void
|
|||
#### Step 5: Add Output Event (Optional)
|
||||
|
||||
**File: `pinch-zoom.component.ts`**
|
||||
|
||||
```typescript
|
||||
export class PinchZoomComponent implements OnInit, OnDestroy {
|
||||
// ... existing outputs
|
||||
|
|
@ -186,6 +179,7 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
#### Step 6: Update IvyPinch Callback
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private zoomChanged: (scale: number, rotation?: number) => void;
|
||||
|
||||
|
|
@ -200,10 +194,7 @@ private handleRotation = (event: any): void => {
|
|||
#### Step 7: Test
|
||||
|
||||
```html
|
||||
<pinch-zoom
|
||||
[enableRotation]="true"
|
||||
[rotationLockThreshold]="15"
|
||||
(rotationChanged)="onRotationChange($event)">
|
||||
<pinch-zoom [enableRotation]="true" [rotationLockThreshold]="15" (rotationChanged)="onRotationChange($event)">
|
||||
<img src="image.jpg" />
|
||||
</pinch-zoom>
|
||||
```
|
||||
|
|
@ -215,20 +206,22 @@ Add custom easing functions for smoother zoom animations.
|
|||
#### Step 1: Define Easing Functions
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
type EasingFunction = 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
||||
|
||||
const easingFunctions = {
|
||||
'linear': (t: number) => t,
|
||||
linear: (t: number) => t,
|
||||
'ease-in': (t: number) => t * t,
|
||||
'ease-out': (t: number) => t * (2 - t),
|
||||
'ease-in-out': (t: number) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
|
||||
'ease-in-out': (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
|
||||
};
|
||||
```
|
||||
|
||||
#### Step 2: Add Property
|
||||
|
||||
**File: `interfaces.ts`**
|
||||
|
||||
```typescript
|
||||
export interface Properties {
|
||||
// ... existing
|
||||
|
|
@ -239,6 +232,7 @@ export interface Properties {
|
|||
#### Step 3: Implement Animated Zoom
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private animateZoom(
|
||||
targetScale: number,
|
||||
|
|
@ -300,6 +294,7 @@ handleTouchStart(event: TouchEvent) {
|
|||
#### Step 2: Fix Initialization
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private handleLinearSwipe = (event: any): void => {
|
||||
// Store initial position on first move
|
||||
|
|
@ -345,6 +340,7 @@ private resetSwipeState(): void {
|
|||
#### Fix 1: Adjust Timing Window
|
||||
|
||||
**File: `touches.ts`**
|
||||
|
||||
```typescript
|
||||
detectDoubleTap(): boolean {
|
||||
const now = Date.now();
|
||||
|
|
@ -425,6 +421,7 @@ detectDoubleTap(): boolean {
|
|||
#### Step 1: Verify Listeners Are Removed
|
||||
|
||||
**File: `touches.ts`**
|
||||
|
||||
```typescript
|
||||
destroy(): void {
|
||||
console.log('[Touches] Removing listeners');
|
||||
|
|
@ -452,6 +449,7 @@ destroy(): void {
|
|||
#### Step 2: Ensure destroy() is Called
|
||||
|
||||
**File: `pinch-zoom.component.ts`**
|
||||
|
||||
```typescript
|
||||
ngOnDestroy(): void {
|
||||
console.log('[PinchZoom] Component destroying');
|
||||
|
|
@ -482,6 +480,7 @@ ngOnDestroy(): void {
|
|||
Zoom to a specific coordinate programmatically.
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
public zoomToPoint(
|
||||
targetScale: number,
|
||||
|
|
@ -555,6 +554,7 @@ private animateZoomToPoint(
|
|||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
// In component
|
||||
@ViewChild('pinchZoom') pinchZoomRef!: PinchZoomComponent;
|
||||
|
|
@ -570,6 +570,7 @@ zoomToTopLeft() {
|
|||
Add a visual indicator of current zoom level.
|
||||
|
||||
**File: `pinch-zoom.component.ts`**
|
||||
|
||||
```typescript
|
||||
export class PinchZoomComponent implements OnInit, OnDestroy {
|
||||
// ... existing code
|
||||
|
|
@ -590,23 +591,25 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
```
|
||||
|
||||
**File: `pinch-zoom.component.html`**
|
||||
|
||||
```html
|
||||
<div class="pinch-zoom-container">
|
||||
<ng-content></ng-content>
|
||||
|
||||
<!-- Zoom indicator -->
|
||||
@if (isZoomedIn()) {
|
||||
<div class="zoom-indicator">
|
||||
<div class="zoom-label">{{ zoomPercentage() }}%</div>
|
||||
<div class="zoom-bar">
|
||||
<div class="zoom-bar-fill" [style.width]="zoomBarWidth()"></div>
|
||||
</div>
|
||||
<div class="zoom-indicator">
|
||||
<div class="zoom-label">{{ zoomPercentage() }}%</div>
|
||||
<div class="zoom-bar">
|
||||
<div class="zoom-bar-fill" [style.width]="zoomBarWidth()"></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
```
|
||||
|
||||
**File: `pinch-zoom.component.sass`**
|
||||
|
||||
```sass
|
||||
.zoom-indicator
|
||||
position: absolute
|
||||
|
|
@ -643,6 +646,7 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
Reduce the number of transform updates during panning.
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private lastPanTime: number = 0;
|
||||
private readonly PAN_THROTTLE_MS = 16; // ~60fps
|
||||
|
|
@ -664,6 +668,7 @@ private handleLinearSwipe = (event: any): void => {
|
|||
Batch transform updates to animation frames.
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private rafId: number | null = null;
|
||||
private pendingTransform: {
|
||||
|
|
@ -712,6 +717,7 @@ public destroy(): void {
|
|||
Avoid expensive calculations on every resize event.
|
||||
|
||||
**File: `ivypinch.ts`**
|
||||
|
||||
```typescript
|
||||
private resizeTimeout: number | null = null;
|
||||
|
||||
|
|
@ -747,6 +753,7 @@ public destroy(): void {
|
|||
Add visual overlay showing current transform state.
|
||||
|
||||
**Create: `transform-debugger.component.ts`**
|
||||
|
||||
```typescript
|
||||
import { Component, input, computed } from '@angular/core';
|
||||
|
||||
|
|
@ -763,37 +770,39 @@ import { Component, input, computed } from '@angular/core';
|
|||
<div class="debug-grid"></div>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
.debug-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
styles: [
|
||||
`
|
||||
.debug-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.debug-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #0f0;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.debug-info {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #0f0;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.debug-grid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0, 255, 0, 0.1) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
`]
|
||||
.debug-grid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0, 255, 0, 0.1) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
export class TransformDebugger {
|
||||
scale = input<number>(1);
|
||||
|
|
@ -805,16 +814,13 @@ export class TransformDebugger {
|
|||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```html
|
||||
<pinch-zoom #pz>
|
||||
<img src="image.jpg" />
|
||||
</pinch-zoom>
|
||||
|
||||
<transform-debugger
|
||||
[scale]="pz.scale()"
|
||||
[moveX]="pz.moveX()"
|
||||
[moveY]="pz.moveY()">
|
||||
</transform-debugger>
|
||||
<transform-debugger [scale]="pz.scale()" [moveX]="pz.moveX()" [moveY]="pz.moveY()"> </transform-debugger>
|
||||
```
|
||||
|
||||
### Technique 2: Event Flow Logging
|
||||
|
|
@ -822,6 +828,7 @@ export class TransformDebugger {
|
|||
Add comprehensive logging to trace event flow.
|
||||
|
||||
**File: `touches.ts`**
|
||||
|
||||
```typescript
|
||||
private logEvent(stage: string, data: any): void {
|
||||
if (!this.debug) return;
|
||||
|
|
@ -853,6 +860,7 @@ handleTouchMove(event: TouchEvent): void {
|
|||
### Unit Testing IvyPinch
|
||||
|
||||
**File: `ivypinch.spec.ts`**
|
||||
|
||||
```typescript
|
||||
import { IvyPinch } from './ivypinch';
|
||||
import { Properties } from './interfaces';
|
||||
|
|
@ -892,8 +900,8 @@ describe('IvyPinch', () => {
|
|||
const mockEvent = {
|
||||
touches: [
|
||||
{ clientX: 0, clientY: 0 },
|
||||
{ clientX: 500, clientY: 0 }
|
||||
]
|
||||
{ clientX: 500, clientY: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
// This would zoom to 5x, but limit is 3x
|
||||
|
|
@ -914,6 +922,7 @@ describe('IvyPinch', () => {
|
|||
### Integration Testing
|
||||
|
||||
**File: `pinch-zoom.component.spec.ts`**
|
||||
|
||||
```typescript
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { PinchZoomComponent } from './pinch-zoom.component';
|
||||
|
|
@ -924,7 +933,7 @@ describe('PinchZoomComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PinchZoomComponent]
|
||||
imports: [PinchZoomComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PinchZoomComponent);
|
||||
|
|
@ -964,6 +973,7 @@ describe('PinchZoomComponent', () => {
|
|||
## Summary
|
||||
|
||||
This guide covers:
|
||||
|
||||
- ✅ Adding new features (rotation, easing)
|
||||
- ✅ Fixing common bugs (jumps, double-tap, memory leaks)
|
||||
- ✅ Common customizations (zoom to point, indicators)
|
||||
|
|
|
|||
100
docs/README.md
100
docs/README.md
|
|
@ -7,25 +7,25 @@ Welcome! This documentation will help you understand and maintain the ngx-pinch-
|
|||
### For New Maintainers - Start Here
|
||||
|
||||
1. **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** ⚡
|
||||
- Fast lookup for common tasks
|
||||
- Code locations
|
||||
- Common patterns
|
||||
- Debugging checklist
|
||||
- **Read this first for quick answers!**
|
||||
- Fast lookup for common tasks
|
||||
- Code locations
|
||||
- Common patterns
|
||||
- Debugging checklist
|
||||
- **Read this first for quick answers!**
|
||||
|
||||
2. **[ARCHITECTURE.md](ARCHITECTURE.md)** 🏗️
|
||||
- Deep dive into how everything works
|
||||
- Component interaction diagrams
|
||||
- Transform mathematics explained
|
||||
- Event flow documentation
|
||||
- **Read this to understand the system**
|
||||
- Deep dive into how everything works
|
||||
- Component interaction diagrams
|
||||
- Transform mathematics explained
|
||||
- Event flow documentation
|
||||
- **Read this to understand the system**
|
||||
|
||||
3. **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** 🛠️
|
||||
- Step-by-step implementation examples
|
||||
- How to add features
|
||||
- How to fix bugs
|
||||
- Code customizations
|
||||
- **Read this when making changes**
|
||||
- Step-by-step implementation examples
|
||||
- How to add features
|
||||
- How to fix bugs
|
||||
- Code customizations
|
||||
- **Read this when making changes**
|
||||
|
||||
### Documentation Organization
|
||||
|
||||
|
|
@ -62,50 +62,50 @@ docs/
|
|||
|
||||
### "Where do I find...?"
|
||||
|
||||
| Question | Answer |
|
||||
|----------|--------|
|
||||
| How pinch zoom works? | ARCHITECTURE.md → "Transform Mathematics" |
|
||||
| How to add an input property? | IMPLEMENTATION_GUIDE.md → "Adding New Features" |
|
||||
| Why isn't pan working? | QUICK_REFERENCE.md → "Debugging Checklist" |
|
||||
| What is `transformElement()`? | ARCHITECTURE.md → "Transform Mathematics" |
|
||||
| How to add rotation? | IMPLEMENTATION_GUIDE.md → "Feature: Add Rotation Support" |
|
||||
| Performance best practices? | QUICK_REFERENCE.md → "Performance Tips" |
|
||||
| Event flow diagram? | ARCHITECTURE.md → "Event System" |
|
||||
| Question | Answer |
|
||||
| ----------------------------- | --------------------------------------------------------- |
|
||||
| How pinch zoom works? | ARCHITECTURE.md → "Transform Mathematics" |
|
||||
| How to add an input property? | IMPLEMENTATION_GUIDE.md → "Adding New Features" |
|
||||
| Why isn't pan working? | QUICK_REFERENCE.md → "Debugging Checklist" |
|
||||
| What is `transformElement()`? | ARCHITECTURE.md → "Transform Mathematics" |
|
||||
| How to add rotation? | IMPLEMENTATION_GUIDE.md → "Feature: Add Rotation Support" |
|
||||
| Performance best practices? | QUICK_REFERENCE.md → "Performance Tips" |
|
||||
| Event flow diagram? | ARCHITECTURE.md → "Event System" |
|
||||
|
||||
### "How do I...?"
|
||||
|
||||
| Task | Document |
|
||||
|------|----------|
|
||||
| Fix image jumping | IMPLEMENTATION_GUIDE.md → "Bug: Image Jumps on First Touch" |
|
||||
| Task | Document |
|
||||
| ------------------------ | ----------------------------------------------------------------------- |
|
||||
| Fix image jumping | IMPLEMENTATION_GUIDE.md → "Bug: Image Jumps on First Touch" |
|
||||
| Add custom zoom behavior | IMPLEMENTATION_GUIDE.md → "Custom: Programmatic Zoom to Specific Point" |
|
||||
| Debug transforms | IMPLEMENTATION_GUIDE.md → "Technique 1: Visual Transform Debugging" |
|
||||
| Improve performance | IMPLEMENTATION_GUIDE.md → "Performance Optimization" |
|
||||
| Add zoom indicator | IMPLEMENTATION_GUIDE.md → "Custom: Zoom Level Indicator" |
|
||||
| Debug transforms | IMPLEMENTATION_GUIDE.md → "Technique 1: Visual Transform Debugging" |
|
||||
| Improve performance | IMPLEMENTATION_GUIDE.md → "Performance Optimization" |
|
||||
| Add zoom indicator | IMPLEMENTATION_GUIDE.md → "Custom: Zoom Level Indicator" |
|
||||
|
||||
## 📖 Reading Code
|
||||
|
||||
### Source Files (In Order of Complexity)
|
||||
|
||||
1. **properties.ts** (Easiest)
|
||||
- Just default configuration values
|
||||
- Good starting point
|
||||
- Just default configuration values
|
||||
- Good starting point
|
||||
|
||||
2. **interfaces.ts** (Easy)
|
||||
- TypeScript type definitions
|
||||
- Understand data structures
|
||||
- TypeScript type definitions
|
||||
- Understand data structures
|
||||
|
||||
3. **pinch-zoom.component.ts** (Medium)
|
||||
- Angular component with signals
|
||||
- See how properties flow
|
||||
- Angular component with signals
|
||||
- See how properties flow
|
||||
|
||||
4. **touches.ts** (Medium-Hard)
|
||||
- Event detection logic
|
||||
- Gesture recognition
|
||||
- Event detection logic
|
||||
- Gesture recognition
|
||||
|
||||
5. **ivypinch.ts** (Hardest)
|
||||
- Core zoom/pan mathematics
|
||||
- Transform calculations
|
||||
- **Most complex file in the library**
|
||||
- Core zoom/pan mathematics
|
||||
- Transform calculations
|
||||
- **Most complex file in the library**
|
||||
|
||||
### Recommended Reading Order
|
||||
|
||||
|
|
@ -157,20 +157,20 @@ flowchart TD
|
|||
### Key Concepts to Understand
|
||||
|
||||
1. **CSS Transform Matrix**
|
||||
- How we apply zoom and pan using a single CSS property
|
||||
- See: ARCHITECTURE.md → "Transform Mathematics"
|
||||
- How we apply zoom and pan using a single CSS property
|
||||
- See: ARCHITECTURE.md → "Transform Mathematics"
|
||||
|
||||
2. **Signal-Based Reactivity**
|
||||
- How Angular signals provide fine-grained updates
|
||||
- See: ARCHITECTURE.md → "Signal Architecture"
|
||||
- How Angular signals provide fine-grained updates
|
||||
- See: ARCHITECTURE.md → "Signal Architecture"
|
||||
|
||||
3. **Gesture State Machine**
|
||||
- How events transition between states
|
||||
- See: QUICK_REFERENCE.md → "Gesture State Machine"
|
||||
- How events transition between states
|
||||
- See: QUICK_REFERENCE.md → "Gesture State Machine"
|
||||
|
||||
4. **Coordinate Systems**
|
||||
- Element-relative vs page-relative positions
|
||||
- See: ARCHITECTURE.md → "Transform Mathematics"
|
||||
- Element-relative vs page-relative positions
|
||||
- See: ARCHITECTURE.md → "Transform Mathematics"
|
||||
|
||||
## 🛠️ Making Changes
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ console.log('[IvyPinch] handlePinch', {
|
|||
scale: this.scale,
|
||||
distance: this.distance,
|
||||
moveX: this.moveX,
|
||||
moveY: this.moveY
|
||||
moveY: this.moveY,
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1003,9 +1003,7 @@ export class IvyPinch {
|
|||
* @returns Distance in pixels
|
||||
*/
|
||||
private getDistance(touches: TouchList): number {
|
||||
return Math.sqrt(
|
||||
Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2),
|
||||
);
|
||||
return Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2));
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
|
|
@ -1132,11 +1130,9 @@ export class IvyPinch {
|
|||
|
||||
// Calculate position to zoom at tap location
|
||||
this.moveX =
|
||||
this.initialMoveX -
|
||||
(changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
|
||||
this.initialMoveX - (changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
|
||||
this.moveY =
|
||||
this.initialMoveY -
|
||||
(changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
|
||||
this.initialMoveY - (changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
|
||||
}
|
||||
// Case 2: Programmatic zoom (zoom at center)
|
||||
else {
|
||||
|
|
@ -1279,10 +1275,7 @@ export class IvyPinch {
|
|||
|
||||
// Check if scaled image exceeds container
|
||||
if (this.scale > 1) {
|
||||
return (
|
||||
imgHeight * this.scale > this.parentElement.offsetHeight ||
|
||||
imgWidth * this.scale > this.parentElement.offsetWidth
|
||||
);
|
||||
return imgHeight * this.scale > this.parentElement.offsetHeight || imgWidth * this.scale > this.parentElement.offsetWidth;
|
||||
}
|
||||
|
||||
// Check if normal image exceeds container
|
||||
|
|
|
|||
|
|
@ -302,7 +302,6 @@ export class IvyPinch {
|
|||
this.scale = newScale;
|
||||
this.zoomChanged(this.scale);
|
||||
|
||||
|
||||
/* Get cursor position over image */
|
||||
const xCenter = event.clientX - this.elementPosition.left - this.initialMoveX;
|
||||
const yCenter = event.clientY - this.elementPosition.top - this.initialMoveY;
|
||||
|
|
@ -538,9 +537,7 @@ export class IvyPinch {
|
|||
}
|
||||
|
||||
private getDistance(touches: TouchList): number {
|
||||
return Math.sqrt(
|
||||
Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2),
|
||||
);
|
||||
return Math.sqrt(Math.pow(touches[0].pageX - touches[1].pageX, 2) + Math.pow(touches[0].pageY - touches[1].pageY, 2));
|
||||
}
|
||||
|
||||
private getImageHeight(): number {
|
||||
|
|
@ -556,15 +553,7 @@ export class IvyPinch {
|
|||
private transformElement(duration: number): void {
|
||||
this.element.style.transition = 'all ' + duration + 'ms';
|
||||
this.element.style.transform =
|
||||
'matrix(' +
|
||||
Number(this.scale) +
|
||||
', 0, 0, ' +
|
||||
Number(this.scale) +
|
||||
', ' +
|
||||
Number(this.moveX) +
|
||||
', ' +
|
||||
Number(this.moveY) +
|
||||
')';
|
||||
'matrix(' + Number(this.scale) + ', 0, 0, ' + Number(this.scale) + ', ' + Number(this.moveX) + ', ' + Number(this.moveY) + ')';
|
||||
}
|
||||
|
||||
private isTouchScreen(): boolean {
|
||||
|
|
@ -593,10 +582,7 @@ export class IvyPinch {
|
|||
const imgWidth = this.getImageWidth();
|
||||
|
||||
if (this.scale > 1) {
|
||||
return (
|
||||
imgHeight * this.scale > this.parentElement.offsetHeight ||
|
||||
imgWidth * this.scale > this.parentElement.offsetWidth
|
||||
);
|
||||
return imgHeight * this.scale > this.parentElement.offsetHeight || imgWidth * this.scale > this.parentElement.offsetWidth;
|
||||
}
|
||||
if (this.scale === 1) {
|
||||
return imgHeight > this.parentElement.offsetHeight || imgWidth > this.parentElement.offsetWidth;
|
||||
|
|
@ -660,11 +646,9 @@ export class IvyPinch {
|
|||
this.scale = this.initialScale * this.properties.doubleTapScale!;
|
||||
this.zoomChanged(this.scale);
|
||||
this.moveX =
|
||||
this.initialMoveX -
|
||||
(changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
|
||||
this.initialMoveX - (changedTouches[0].clientX - this.elementPosition.left) * (this.properties.doubleTapScale - 1);
|
||||
this.moveY =
|
||||
this.initialMoveY -
|
||||
(changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
|
||||
this.initialMoveY - (changedTouches[0].clientY - this.elementPosition.top) * (this.properties.doubleTapScale - 1);
|
||||
} else {
|
||||
const zoomControlScale = this.properties.zoomControlScale || 0;
|
||||
this.scale = this.initialScale * (zoomControlScale + 1);
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
<div class="pinch-zoom-content" [class.pz-dragging]="isDragging()">
|
||||
<ng-content></ng-content>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Controls container at bottom center -->
|
||||
@if (isControl() || isBrightnessControl()) {
|
||||
<div class="pz-controls-container">
|
||||
<!-- Zoom control -->
|
||||
@if (isControl()) {
|
||||
<div
|
||||
class="pz-zoom-button"
|
||||
[class.pz-zoom-button-out]="isZoomedIn()"
|
||||
(click)="toggleZoom()"
|
||||
(keydown.enter)="toggleZoom()"
|
||||
(keydown.space)="toggleZoom(); $event.preventDefault()"
|
||||
role="button"
|
||||
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
|
||||
tabindex="0"
|
||||
></div>
|
||||
}
|
||||
<div class="pz-controls-container">
|
||||
<!-- Zoom control -->
|
||||
@if (isControl()) {
|
||||
<div
|
||||
class="pz-zoom-button"
|
||||
[class.pz-zoom-button-out]="isZoomedIn()"
|
||||
(click)="toggleZoom()"
|
||||
(keydown.enter)="toggleZoom()"
|
||||
(keydown.space)="toggleZoom(); $event.preventDefault()"
|
||||
role="button"
|
||||
[attr.aria-label]="isZoomedIn() ? 'Zoom out' : 'Zoom in'"
|
||||
tabindex="0"
|
||||
></div>
|
||||
}
|
||||
|
||||
<!-- Brightness controls -->
|
||||
@if (isBrightnessControl()) {
|
||||
<div class="pz-brightness-controls">
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-decrease"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMin()"
|
||||
(click)="brightnessOut()"
|
||||
(keydown.enter)="brightnessOut()"
|
||||
(keydown.space)="brightnessOut(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Decrease brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-increase"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMax()"
|
||||
(click)="brightnessIn()"
|
||||
(keydown.enter)="brightnessIn()"
|
||||
(keydown.space)="brightnessIn(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Increase brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Brightness controls -->
|
||||
@if (isBrightnessControl()) {
|
||||
<div class="pz-brightness-controls">
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-decrease"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMin()"
|
||||
(click)="brightnessOut()"
|
||||
(keydown.enter)="brightnessOut()"
|
||||
(keydown.space)="brightnessOut(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Decrease brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
<div
|
||||
class="pz-brightness-button pz-brightness-increase"
|
||||
[class.pz-brightness-button-disabled]="isBrightnessAtMax()"
|
||||
(click)="brightnessIn()"
|
||||
(keydown.enter)="brightnessIn()"
|
||||
(keydown.space)="brightnessIn(); $event.preventDefault()"
|
||||
role="button"
|
||||
aria-label="Increase brightness"
|
||||
tabindex="0"
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,4 @@
|
|||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
HostBinding,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
computed,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
effect,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { Component, ElementRef, HostBinding, OnInit, OnDestroy, computed, input, output, signal, effect, inject } from '@angular/core';
|
||||
|
||||
import { Properties } from './interfaces';
|
||||
import { defaultProperties } from './properties';
|
||||
|
|
@ -269,20 +257,14 @@ export class PinchZoomComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
brightnessIn(): number {
|
||||
const newBrightness = Math.min(
|
||||
this.brightness() + this.brightnessStep(),
|
||||
this.maxBrightness()
|
||||
);
|
||||
const newBrightness = Math.min(this.brightness() + this.brightnessStep(), this.maxBrightness());
|
||||
this.currentBrightness.set(newBrightness);
|
||||
this.brightnessChanged.emit(newBrightness);
|
||||
return newBrightness;
|
||||
}
|
||||
|
||||
brightnessOut(): number {
|
||||
const newBrightness = Math.max(
|
||||
this.brightness() - this.brightnessStep(),
|
||||
this.minBrightness()
|
||||
);
|
||||
const newBrightness = Math.max(this.brightness() - this.brightnessStep(), this.minBrightness());
|
||||
this.currentBrightness.set(newBrightness);
|
||||
this.brightnessChanged.emit(newBrightness);
|
||||
return newBrightness;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export type EventType =
|
|||
| 'wheel'
|
||||
| 'double-tap'
|
||||
| 'resize';
|
||||
export type TouchHandler ='handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
|
||||
export type TouchHandler = 'handleTouchstart' | 'handleTouchmove' | 'handleTouchend';
|
||||
export type MouseHandler = 'handleMousedown' | 'handleMousemove' | 'handleMouseup' | 'handleWheel';
|
||||
export type OtherHandler = 'handleResize';
|
||||
|
||||
|
|
@ -124,10 +124,7 @@ export class Touches {
|
|||
}
|
||||
|
||||
// Determine target element (Window, Document, or this.element)
|
||||
const target =
|
||||
listener === 'resize' ? window :
|
||||
(listener === 'mouseup' || listener === 'mousemove') ? document :
|
||||
this.element;
|
||||
const target = listener === 'resize' ? window : listener === 'mouseup' || listener === 'mousemove' ? document : this.element;
|
||||
|
||||
// Add or remove the listener
|
||||
if (action === 'addEventListener') {
|
||||
|
|
@ -280,11 +277,7 @@ export class Touches {
|
|||
}
|
||||
|
||||
// Linear swipe
|
||||
if (
|
||||
this.detectLinearSwipe(event) ||
|
||||
this.eventType === 'horizontal-swipe' ||
|
||||
this.eventType === 'vertical-swipe'
|
||||
) {
|
||||
if (this.detectLinearSwipe(event) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
|
||||
this.handleLinearSwipe(event);
|
||||
}
|
||||
};
|
||||
|
|
@ -378,11 +371,7 @@ export class Touches {
|
|||
const touches = (event as TouchEvent).touches;
|
||||
|
||||
if (touches) {
|
||||
if (
|
||||
(touches.length === 1 && !this.eventType) ||
|
||||
this.eventType === 'horizontal-swipe' ||
|
||||
this.eventType === 'vertical-swipe'
|
||||
) {
|
||||
if ((touches.length === 1 && !this.eventType) || this.eventType === 'horizontal-swipe' || this.eventType === 'vertical-swipe') {
|
||||
return this.getLinearSwipeType(event);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div class="demo">
|
||||
<h2>transition-duration: 1000</h2>
|
||||
<p>Defines the speed of the animation of positioning and transforming.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [transition-duration]="1000" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<div class="demo">
|
||||
<h2>limit-zoom: 2</h2>
|
||||
<p>Limit the maximum available scale. By default, the maximum scale is calculated based on the original image size.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [limit-zoom]="2" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<div class="demo">
|
||||
<h2>minScale: 1</h2>
|
||||
<p>Limit the minimum acceptable scale. With a value of 1, it is recommended to use this parameter with limitPan.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [minScale]="1" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -41,7 +41,7 @@
|
|||
<div class="demo">
|
||||
<h2>auto-zoom-out: true</h2>
|
||||
<p>Automatic restoration of the original size of an image after its zooming in by two fingers.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [auto-zoom-out]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
<div class="demo">
|
||||
<h2>double-tap: false</h2>
|
||||
<p>Zooming in and zooming out of an image, depending on its current condition, with double tap.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [double-tap]="false" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
<div class="demo">
|
||||
<h2>double-tap-scale: 4</h2>
|
||||
<p>Double tap scaling factor.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [double-tap-scale]="4" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
<!-- disabled -->
|
||||
<div class="demo">
|
||||
<h2>disabled: true</h2>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [disabled]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
<div class="demo">
|
||||
<h2>disablePan: true</h2>
|
||||
<p>Turn off panning with one finger.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [disablePan]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -100,7 +100,7 @@
|
|||
<div class="demo">
|
||||
<h2>minPanScale: 2</h2>
|
||||
<p>Minimum zoom at which panning is enabled.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [minPanScale]="2" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
hidden - the overflow is clipped, and the rest of the content will be invisible. visible - the overflow is not clipped. The
|
||||
content renders outside the element's box.
|
||||
</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [overflow]="'visible'" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
Disable zoom controls. auto - Disable zoom controls on touch screen devices. never - show zoom controls on all devices. disable
|
||||
- disable zoom controls on all devices.
|
||||
</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [disableZoomControl]="'disable'" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -142,7 +142,7 @@
|
|||
<div class="demo">
|
||||
<h2>zoomControlScale: 2</h2>
|
||||
<p>Zoom factor when using zoom controls.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [zoomControlScale]="2" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
<div class="demo">
|
||||
<h2>backgroundColor: 'rgba(0,0,0,0.65)'</h2>
|
||||
<p>The background color of the container.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [backgroundColor]="'rgba(0,0,0,0.65)'" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -166,7 +166,7 @@
|
|||
<div class="demo">
|
||||
<h2>limitPan: true</h2>
|
||||
<p>Stop panning when the edge of the image reaches the edge of the screen.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [limitPan]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
By default, subscriptions are made for mouse and touch screen events. The value auto means that the subscription will be only
|
||||
for touch events or only for mouse events, depending on the type of screen.
|
||||
</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [listeners]="'auto'" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -193,7 +193,7 @@
|
|||
<div class="demo">
|
||||
<h2>wheel: false</h2>
|
||||
<p>Scale with the mouse wheel.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [wheel]="false" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -205,7 +205,7 @@
|
|||
<div class="demo">
|
||||
<h2>wheelZoomFactor: 0.5</h2>
|
||||
<p>Zoom factor when zoomed in with the mouse wheel.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [wheelZoomFactor]="0.5" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -223,7 +223,7 @@
|
|||
image, then specify the attributes width and height for the <img /> tag. When setting the property value to `true`, a
|
||||
subscription to the window resize listener will be created.
|
||||
</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [autoHeight]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
<div class="demo">
|
||||
<h2>draggableImage: true</h2>
|
||||
<p>Sets the attribute draggable to the img tag.</p>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [draggableImage]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
<!-- draggableImage -->
|
||||
<div class="demo">
|
||||
<h2>draggableOnPinch: true</h2>
|
||||
<p>Zoom State: {{this.zoomstate}}</p>
|
||||
<p>Zoom State: {{ this.zoomstate }}</p>
|
||||
<pinch-zoom [draggableOnPinch]="true" (zoomChanged)="onZoomChanged($event)">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1577234162223-98e9caa94705?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=752&q=80"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Component } from '@angular/core';
|
|||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.sass'],
|
||||
standalone: false
|
||||
standalone: false,
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'ivypinchApp';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import {PinchZoomComponent} from "ngx-pinch-zoom";
|
||||
import { PinchZoomComponent } from 'ngx-pinch-zoom';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue