1
 25

Draggable

The class Draggable adds the ability to move an element with the mouse or with a finger.

Click in the image to start the animation. Click again to stop it. Move the pointer of the mouse over the image and press the space bar. Drag the clip within the <div> which contains it with the mouse or with a finger on a touch screen. Move the clip close to the right side of the <div>. Try resizing it.

  1. function Draggable() {
  2.     View.call(this);
  3.  
  4.     this._draggable = false;
  5.     this._dragged = false;
  6. }
  7.  
  8. Draggable.prototype = Object.create(View.prototype);
  9.  
  10. Object.defineProperty(Draggable.prototype, 'constructor', { value: Draggable, enumerable: false, writable: true });
  11.  
  12. Draggable.prototype.isDraggable = function() {
  13.     return this._draggable;
  14. };
  15.  
  16. Draggable.prototype.enableDrag = function() {
  17.     if (this._draggable)
  18.         return this;
  19.  
  20.     if (!this.isManaged())
  21.         return this;
  22.  
  23.     if (this._drag === undefined) {
  24.         let xmax, ymax;
  25.         let x0, y0;
  26.  
  27.         this._drag = (e) => {
  28.             let x1, y1;
  29.  
  30.             switch (e.type) {
  31.                 case 'mousemove':
  32.                     x1 = Math.max(e.clientX, 0);
  33.                     y1 = Math.max(e.clientY, 0);
  34.                     break;
  35.  
  36.                 case 'touchmove':
  37.                     x1 = Math.max(e.changedTouches[0].clientX, 0);
  38.                     y1 = Math.max(e.changedTouches[0].clientY, 0);
  39.                     break;
  40.  
  41.                 default:
  42.                     return;
  43.             }
  44.  
  45.             const dx = x0 - x1;
  46.             const dy = y0 - y1;
  47.  
  48.             if (dx == 0 && dy == 0)
  49.                 return;
  50.  
  51.             x0 = x1;
  52.             y0 = y1;
  53.  
  54.             const w = this._widget;
  55.  
  56.             const x = Math.max(0, Math.min(xmax, w.offsetLeft - dx));
  57.             const y = Math.max(0, Math.min(ymax, w.offsetTop - dy));
  58.  
  59.             w.style.left = x + 'px';
  60.             w.style.top = y + 'px';
  61.  
  62.             this._dragged = true;
  63.         };
  64.  
  65.         this._startdrag = (e) => {
  66.             switch (e.type) {
  67.                 case 'mousedown':
  68.                     x0 = e.clientX;
  69.                     y0 = e.clientY;
  70.  
  71.                     document.addEventListener('mousemove', this._drag);
  72.                     document.addEventListener('mouseup', this._stopdrag);
  73.  
  74.                     e.preventDefault();
  75.                     break;
  76.  
  77.                 case 'touchstart':
  78.                     x0 = e.touches[0].clientX;
  79.                     y0 = e.touches[0].clientY;
  80.  
  81.                     document.addEventListener('touchmove', this._drag);
  82.                     document.addEventListener('touchend', this._stopdrag);
  83.                     break;
  84.  
  85.                 default:
  86.                     return;
  87.             }
  88.  
  89.             this._dragged = false;
  90.         };
  91.  
  92.         this._stopdrag = (e) => {
  93.             switch (e.type) {
  94.                 case 'mouseup':
  95.                     document.removeEventListener('mousemove', this._drag);
  96.                     document.removeEventListener('mouseup', this._stopdrag);
  97.  
  98.                     e.preventDefault();
  99.                     break;
  100.  
  101.                 case 'touchend':
  102.                     document.removeEventListener('touchmove', this._drag);
  103.                     document.removeEventListener('touchend', this._stopdrag);
  104.                     break;
  105.  
  106.                 default:
  107.                     return;
  108.             }
  109.         };
  110.  
  111.         this._fitin = () => {
  112.             const w = this._widget;
  113.             const cw = this._parent;
  114.  
  115.             xmax = cw.offsetWidth - w.width;
  116.             ymax = cw.offsetHeight - w.height;
  117.  
  118.             const x = Math.max(0, Math.min(xmax, w.offsetLeft));
  119.             const y = Math.max(0, Math.min(ymax, w.offsetTop));
  120.  
  121.             if (x != w.offsetLeft || y != w.offsetTop) {
  122.                 w.style.left = x + 'px';
  123.                 w.style.top = y + 'px';
  124.             }
  125.         };
  126.  
  127.         this._dragobserver = new ResizeObserver(this._fitin);
  128.     }
  129.  
  130.     this._parent.style.position = 'relative';
  131.  
  132.     this._dragobserver.observe(this._parent);
  133.  
  134.     this.addEventListener('mousedown', this._startdrag);
  135.     this.addEventListener('touchstart', this._startdrag);
  136.  
  137.     this.setStyle('position', 'absolute');
  138.  
  139.     this.setStyle('touchAction', 'none');
  140.  
  141.     this._draggable = true;
  142.     this._dragged = false;
  143.  
  144.     return this;
  145. };
  146.  
  147. Draggable.prototype.disableDrag = function() {
  148.     if (!this._draggable)
  149.         return this;
  150.  
  151.     this._dragobserver.unobserve(this._parent);
  152.  
  153.     this.removeEventListener('touchstart', this._startdrag);
  154.     this.removeEventListener('mousedown', this._startdrag);
  155.  
  156.     this.setStyle('touchAction', 'auto');
  157.  
  158.     this._draggable = false;
  159.     this._dragged = false;
  160.  
  161.     return this;
  162. };
Test
  1. function NoiseClip() {
  2.     ProgramClip.call(this);
  3.  
  4.     this._interval = NoiseClip.playbackRate;
  5.  
  6.     this._imageData = null;
  7. }
  8.  
  9. NoiseClip.prototype = Object.create(ProgramClip.prototype);
  10.  
  11. Object.defineProperty(NoiseClip.prototype, 'constructor', { value: NoiseClip, enumerable: false, writable: true });
  12.  
  13. NoiseClip.playbackRate = 100;

An instance of NoiseClip inhérits from the class ProgramClip. The property _interval is inherited from the class Clip. It defines the animation rate of the clip. The property _imagedata is an object ImageData which contains the data of the pixels of the canvas of the image of the clip.

  1. Object.assign(NoiseClip.prototype, Draggable.prototype);

Extends the class NoiseClip with the class Draggable.

  1. Object.defineProperty(NoiseClip.prototype, 'playbackRate', {
  2.     get:    function() {
  3.         return this._playbackRate;
  4.     },
  5.     set:    function(r) {
  6.         if (typeof r !== 'number')
  7.             throw new TypeError();
  8.  
  9.         if (r != NoiseClip.playbackRate)
  10.             throw new RangeError();
  11.     }
  12. });

Redefines the accessor playrate inherited from the class ProgramClip. playrate prevents any modification of the animation rate of an instance of NoiseClip.

  1. NoiseClip.prototype.enablePlayer = function() {
  2.     if (this._player)
  3.         return this;
  4.  
  5.     if (this._click === undefined) {
  6.         this._click = (e) => {
  7.             switch (e.type) {
  8.                 case 'click':
  9.                     if (this._dragged)
  10.                         break;
  11.  
  12.                     if (this._paused)
  13.                         this.play();
  14.                     else
  15.                         this.pause();
  16.                     break;
  17.             }
  18.         };
  19.     }
  20.  
  21.     if (this._keydown === undefined) {
  22.         this._keydown = (e) => {
  23.             e.preventDefault();
  24.  
  25.             switch (e.key) {
  26.                 case ' ':
  27.                     if (this._paused)
  28.                         this.play();
  29.                     else
  30.                         this.pause();
  31.                     break;
  32.             }
  33.         };
  34.     }
  35.  
  36.     ProgramClip.prototype.enablePlayer.call(this);
  37.  
  38.     return this;
  39. };

enablePlayer redefines the method inherited from the Clip class.

  1. NoiseClip.prototype.setWidget = function(w) {
  2.     if (w.tagName != 'CANVAS')
  3.         throw new TypeError();
  4.  
  5.     if (w.width == 0 || w.height == 0)
  6.         throw new TypeError();
  7.  
  8.     ProgramClip.prototype.setWidget.call(this, w);
  9.  
  10.     const ctx = w.getContext('2d');
  11.  
  12.     const imgdata = ctx.createImageData(w.width, w.height);
  13.  
  14.     const size = imgdata.width * imgdata.height * 4;
  15.  
  16.     for (let i = 0; i < size; i += 4)
  17.         imgdata.data[i+3] = 255;
  18.  
  19.     this._imageData = imgdata;
  20.  
  21.     window.addEventListener('load', () => this.drawWidget());
  22.  
  23.     return this;
  24. };

setWidget redefines the method inherited from the class Clip.

  1. NoiseClip.prototype.drawWidget = function() {
  2.     const imgdata = this._imageData;
  3.  
  4.     const size = imgdata.width * imgdata.height * 4;
  5.  
  6.     for (let i = 0; i < size; i += 4) {
  7.         imgdata.data[i+0] = Math.random() > 0.5 ? 255 : 0;
  8.         imgdata.data[i+1] = Math.random() > 0.5 ? 255 : 0;
  9.         imgdata.data[i+2] = Math.random() > 0.5 ? 255 : 0;
  10.     }
  11.  
  12.     const ctx = this._widget.getContext('2d');
  13.  
  14.     ctx.putImageData(imgdata, 0, 0);
  15.  
  16.     return this;
  17. };

drawWidget redefines the method inherited from the class ProgramClip.

  1. <?php $size=100; ?>

Defines the size of the image of the clip, a square, in pixels.

  1. <?php $id=uniqid('id'); ?>

Defines the identifier of the <div> which surrounds the HTML of the clip.

  1. #<?php echo $id; ?> {
  2.     min-width: <?php echo $size+20; ?>px;
  3.     min-height: <?php echo $size+20; ?>px;
  4.     max-width: 400px;
  5.     max-height: 200px;
  6.     resize: both;
  7.     overflow: hidden;
  8.     background-color: #fcc;
  9.     background-image: linear-gradient(to right bottom, white 5%, transparent 75%);
  10. }
  11. @media screen and (max-width:410px) {
  12.     #<?php echo $id; ?> {
  13.         max-width: 100%;
  14.     }
  15. }

Defines the CSS of the <div> which contains the clip.

  1. <div id="<?php echo $id; ?>" class="noprint">
  2. <canvas width="<?php echo $size; ?>" height="<?php echo $size; ?>"></canvas>
  3. </div>

The image of the clip is drawn in a canvas.

  1. <?php head('javascript', '/objectivejs/Objective.js'); ?>
  2. <?php head('javascript', '/objectivejs/Responder.js'); ?>
  3. <?php head('javascript', '/objectivejs/View.js'); ?>
  4. <?php head('javascript', '/objectivejs/Clip.js'); ?>
  5. <?php head('javascript', '/objectivejs/ProgramClip.js'); ?>
  6. <?php head('javascript', '/objectivejs/Draggable.js'); ?>
  7. <?php head('javascript', '/objectivejs/tests/NoiseClip.js'); ?>

Includes the code of all the necessary classes. REMINDER: The function head of the iZend library adds a tag such as <script src="/objectivejs/Objective.js"></script> to the <head> section of the document in HTML. Adapt the code to your development environment.

  1. const noise = new NoiseClip();
  2.  
  3. noise.setManagedWidget(document.querySelector('#<?php echo $id; ?> canvas'));

Creates an instance of NoiseClip and associates it to the canvas. The initial image is automatically drawn as soon as the window is completely loaded.

  1. noise.enablePlayer();

Activates the control of the animation.

  1. noise.enableDrag();

Activates the control of the movement of the clip.

SEE ALSO

ProgramClip, Clip

Comments

Your comment:
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip help 2000

Enter a maximum of 2000 characters.
Improve the presentation of your text with the following formatting tags:
[p]paragraph[/p], [b]bold[/b], [i]italics[/i], [u]underline[/u], [s]strike[/s], [quote]citation[/quote], [pre]as is[/pre], [br]line break,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]command[/code], [code=language]source code in c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].