Vanilla Js

Object

Dictionary Parser

  • json
JSON.stringify(jsObj, null, 4);
  • extends values

const data = {
    key: "value",
}

const paths = Object.fromEntries(
  Object.entries(data).map(([key, value]) => [key, extends(value)]),
);

undefined

undefined && true // undefined
undefined && false // undefined
!!undefined // false
!undefined // true
Boolean(undefined) // false

ObjectURL

  • Parser canvas to objectURL.
canvas.toBlob((blob) => URL.createObjectURL(blob));
  • Releases an existing object URL.
URL.revokeObjectURL(objectURL);

Array

  • search and remove object from array
function removeFromArray( array, obj )
{
    const index = array.indexOf( obj )
    if ( index > -1 )
    {
        array.splice( index, 1 );
    }
}

Request

fetch('http://example.com/movies.json').then(function(response) {
    return response.json();
}).then(function(myJson) {
    console.log(myJson);
});

Operator

Bitwise Tag

export class Property
{
    static SHOWMODAL = 0b0001;
    static EDIT = 0b0010;
    static checkProperty( value: number, type: number )
    {
        return ( value & type ) === type
    }
}

Spread Operator

[...Array(2)].map((_,idx)=>idx) //[0,1]

const arr = ['a','b','c']
[...arr,'d','e'] //['a','b','c','d','e']

{...arr} //{0: 'a', 1: 'b', 2: 'c'}

Redirect

Current Page

function redirect(url) {
    window.location.href = url;   
}

New Tab

function openInNewTab(url) {
    const win = window.open(url, '_blank');
    win.focus();
}

Debug

  • log color in console
    const css = `background: rgba(${zeroTo255R},${zeroTo255G},${zeroTo255B},${1.0});`
    console.log('%c color', css)
    

Elements

Window

  • Focus on element with id.
window.location.hash = "element_id";
// <div id="element_id"/>

Document

  • Triggering onBlur in JavaScript is useful when implementing the fouc-within CSS style
const focusedElement = document.activeElement;
if (focusedElement) {
    focusedElement.blur();
}

Input/Textarea

  • judge next character will make element overflow
export function isInputOverflow(element, text) {
  const tempSpan = document.createElement("span");
  tempSpan.style.visibility = "hidden";
  tempSpan.style.whiteSpace = "nowrap";
  tempSpan.style.font = window.getComputedStyle(element).font;
  tempSpan.textContent = text;
  document.body.appendChild(tempSpan);

  const isOverflow = tempSpan.offsetWidth > element.offsetWidth;

  document.body.removeChild(tempSpan);

  return isOverflow;
}

// scrollHeight including padding, border and the invisible area, so make sure padding and border is 0px or subtract them.
export function isTextareaOverflow(element) {
  return element.scrollHeight > element.clientHeight;
}

Canvas

  • Avoid applying CSS transform to the canvas size, as it may cause blurriness on iOS Safari.

  • There are pixel numbers limitation, test report.

const IOS_SAFARI_MAX_CANVAS_PIXELS = 16777216
const pixel =
      divRef.current.clientWidth *
      divRef.current.clientHeight *
      window.devicePixelRatio *
      window.devicePixelRatio;

const maxScale = Math.sqrt(IOS_SAFARI_MAX_CANVAS_PIXELS / pixel) - 1e-1;
  • Render content only in mask, and circle brush for cleaning mask.

const drawCircle = (ctx, x, y, radius) => {
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI * 2);
    ctx.fill();
};

const drawLine = (ctx, x1, y1, x2, y2 lineWidth) => {
    ctx.lineWidth = lineWidth;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();

    const radius = lineWidth / 2;
    drawCircle(ctx, x1, y1, radius);
    drawCircle(ctx, x2, y2, radius);
};

const drawMask = (ctx, x, y, isErasing, radius) => {
 ctx.globalCompositeOperation = isErasing
      ? "destination-out"
      : "source-over";
      
  // drawCircle(...) or drawLine(...)

  ctx.globalCompositeOperation = "source-over";
};

const applyMask = (mask, canvas, result) => {
  const ctx = result.getContext("2d");

  ctx.clearRect(0, 0, result.width, result.height);

  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(mask, 0, 0);

  ctx.globalCompositeOperation = "source-in";
  ctx.drawImage(canvas, 0, 0);

  ctx.globalCompositeOperation = "source-over";
};

Iframe

  • Embed apps in iframe should consider SameSite. (reference)

// client-side: set iframe webpage's cookie with js-cookie packages
Cookies.set("access_token", accessToken, {
    sameSite: "None",
    secure: true,
});

// server-side: set parent cookie from iframe webpage.
res.cookie("name", value, {
    samSite: "None",
    secure: true,
});
  • Make iframe content bigger
iframe {
    width: 200%;
    height: 200%;
    transform: scale(0.5);
    transform-origin: 0 0;
}

Events

Mouse

  • get cursor position
function updateMouse(event, mouse: THREE.Vector2) {
    if (event.type.includes('touch')) {
        mouse.x = event.changedTouches[0].clientX;
        mouse.y = event.changedTouches[0].clientY;
    } else {
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    }
}

Touch

  • Disable pinch-zoom on mobile.

    • Andriod device
       <meta
        name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
      />
    
    • IOS device
      function preventDefault(e) {
          e.preventDefault();
      }
      document.addEventListener("gesturestart", preventDefault);
      document.addEventListener("gesturechange", preventDefault);
      document.addEventListener("gestureend", preventDefault);
    

    Iframe should use the same settings for IOS device, because settings on parent webpage wont block iframe's behavior.

    • Disable pull to refresh on IOS
        html {
      overflow: hidden;
        }
      

Keyboard

  • check number
const isNumber = (key) => {
    return /^[0-9]$/i.test(key);
};
  • ctrl+c (only support https webpage, failed in http)
navigator.clipboard.writeText("text to copy")

MediaRecorder

  • Stream recoder for canvas

const recorder = (() => {
  let mediaRecorder;

  const start = (canvas) => {
    const stream = canvas.captureStream();
    mediaRecorder = new MediaRecorder(stream);
    mediaRecorder.start();
  };

  const stop = () => {
    const recordedBlobs = [];
    const handleDataAvailable = (event) => {
      if (event.data && event.data.size > 0) {
        recordedBlobs.push(event.data);
      }
    };
    return new Promise((resolve) => {
      mediaRecorder.ondataavailable = handleDataAvailable;
      mediaRecorder.onstop = () => {
        resolve(new Blob(recordedBlobs, { type: "video/mp4" }));
      };
    });
  };

  const reset = () => {
    mediaRecorder = null;
  };

  return {
    start,
    stop,
    reset,
  };
})();

  • Stream recoder for camera, share screen.
import { v4 as uuid } from "uuid";

const Recorder = (() => {
  const setStreamToVideoElement = (stream, element) => {
    const { width, height } = stream.getVideoTracks()[0].getSettings();
    element.width = width;
    element.height = height;
    element.autoplay = true;
    element.srcObject = new MediaStream(stream.getTracks());
  };

  const openStream = (starter) => {
    return async (video) => {
      const streamIsCurrentTab = (() => {
        try {
          const id = uuid();
          navigator.mediaDevices.setCaptureHandleConfig({
            handle: id,
            permittedOrigins: ["*"],
          });
          return (stream) => {
            const [track] = stream.getVideoTracks();

            return (
              track.getCaptureHandle &&
              track.getCaptureHandle() &&
              track.getCaptureHandle().handle === id
            );
          };
        } catch (e) {
          console.warn("setCaptureHandleConfig is not supported:", e);
          return () => true;
        }
      })();

      const stream = await starter();
      if (stream && video) {
        setStreamToVideoElement(stream, video);
      }
      if (stream) {
        const close = () => stream.getTracks().forEach((track) => track.stop());

        const record = (onRecordDone) => {
          const supportType = ["video/mp4", "video/webm; codecs=vp9"].find(
            (type) => MediaRecorder.isTypeSupported(type)
          );
          if (!supportType) {
            console.warn("recorder cant found support type.");
            return;
          }

          const encoderOptions = { mimeType: supportType };
          const mediaRecorder = new MediaRecorder(stream, encoderOptions);

          const handleDataAvailable = (event) => {
            if (event.data.size > 0) {
              const blob = new Blob([event.data], {
                type: supportType,
              });
              onRecordDone(blob);
            } else {
              console.warn("stream data not available");
              onRecordDone(null);
            }
          };

          mediaRecorder.ondataavailable = handleDataAvailable;
          mediaRecorder.start();
          return () => mediaRecorder.stop();
        };

        const isCapturingCurrent = () => streamIsCurrentTab(stream);

        return { close, record, isCapturingCurrent };
      }
    };
  };

  const openCamera = openStream(() =>
    navigator.mediaDevices.getUserMedia({
      video: true,
      audio: { deviceId: { ideal: "communications" } },
    })
  );

  const shareScreen = openStream(() =>
    navigator.mediaDevices.getDisplayMedia({
      video: true,
      audio: true,
      preferCurrentTab: true,
    })
  );

  return { openCamera, shareScreen };
})();

export default Recorder;