Skip to content

A fun little project to experiment with mix-blend-mode and drawing with CanvasRenderingContext2D.

License

Notifications You must be signed in to change notification settings

bnjmnrsh/searchlights

Repository files navigation

searchLights.js

A fun little project to experiment with mix-blend-mode and drawing with CanvasRenderingContext2D.

There are two demos on CodePen: using DOM <canvas> elements for configuration using JavaScript Objects for configuration

There were a number of project goals:

  1. The plugin should be 'plug and play' running out of the box with some fun defaults.
  2. These defaults should be able to be overwritten with user options provided by either:
  • The existence of DOM elements with the appropriate class and data attributes.
  • or, programmatically, via an options object searchLights({options}).
  1. The plugin should be extendable via custom events, callbacks, and or by overwriting the public methods with your own to modify functionality.

Basic Use

Fundamentally, you can just plunk the searchLights.js script in the footer of your HTML with an inline script instantiating the plugin. (i.e. the script is an iife instantiated onto window - yolo. )

<script src="./searchLights.js" />
<script>searchLights._init()</script>

Known accessibility issues

The elements added by searchLight() (such as <canvas>), when tied to pointer tracking, can interfere with elements on a page, such as selecting text. If this occurs, you may adjust the z-index value of the selector.

Browser Support

searchlights.js will test the browser on instantiation, adding the class mix-blend-mode. If the browser doesn't report support, the script will exit silently.

Note: Safari has partial support and will return true, though some modes are not supported, including hue, saturation, colour, and luminosity

https://caniuse.com/css-mixblendmode

Blending mode keyword values

https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode

/* Keyword values */
mix-blend-mode: normal;
mix-blend-mode: multiply;
mix-blend-mode: screen;
mix-blend-mode: overlay;
mix-blend-mode: darken;
mix-blend-mode: lighten;
mix-blend-mode: color-dodge;
mix-blend-mode: color-burn;
mix-blend-mode: hard-light;
mix-blend-mode: soft-light;
mix-blend-mode: difference;
mix-blend-mode: exclusion;
mix-blend-mode: hue;
mix-blend-mode: saturation; *
mix-blend-mode: color; *
mix-blend-mode: luminosity; *

/* Global values */
mix-blend-mode: initial;
mix-blend-mode: inherit;
mix-blend-mode: unset;

* These values not supported in Safari; see Browser Support

Styles

.mix-blend-mode .searchlight {
    position: absolute;
    will-change: transform, opacity, left, top;
    opacity: 0;
    display: none;
}

Configurations

Name Value Defaults Description
searchLights {object} undefined The searchLights object is the public API of searchLights.js and exposes a number of methods and options.
searchLights.enableShowHide bool true Set via searchlights({enableShowHide: bool}) This flag which enables the fade out of searchLight elements when the pointer exits the when it encounters preexisting .searchlight elements.
searchLights.isDOM bool undefined This top-level flag is set when searchLights.js encounters preexisting .searchlight elements in the DOM.
searchLights.useInlineStyles bool true Set via options, useInlineStyles disables the writing of styles by plugin. Use if you want to control styles purely in your style sheets. If this flag is false then the only inline element styles that are written, will be to transform to centre the element under pointer, and the dynamic positioning of the elements to follow the pointer.
searchLights.targetClass string searchlight User settable flag via searchLights.targetClass. It is the css class to be added to any searchLights DOM element.
searchLights. parental string document.body Is the DOM element to which programmatically generated searchlight elements will be appended. This element is also the target for pointer enter/exit events, meaning by default, the opacity of searchlights goes to 0 on the exit of the parental element and to the default opacity when it re-enters. See searchLights.enableShowHide.
searchLights.options {object} undefined The options object is the primary object of the public API. It is best explained with some example settings.
searchLights.options.blur int 3 Settable via searchLights({options.blur}). This value sets the global default edge blur value in px of the drawn searchlight elements.
searchLights.options.color string null User settable via the options object. Default undefined, takes any valid css color value. This top-level option will become the default color of the pixels drawn for each searchlight value if they are not specifically set in the individual elements of the searchLights.options.ptrEls array.
searchLights.options.dia int 100 User settable via options. dia sets the diameter in px of the drawn searchlight elements.
searchLights.options.height searchLights.options.width int undefined These values are calculated dynamically and are placeholders for future work.
searchLights.options.blend string screen User settable via the options object. blend sets the css mix-blend-mode of searchlight elements. Full list of blending mode keyword values
searchLights.options.opacity float .8 User settable via the options object. opacity sets the inline CSS opacity of searchlight elements. CSS uses values between 0 and 1.
searchLights.options.easing string ease-out User settable via the options object. easing sets the inline CSS transition-timing-function of searchlight elements. Full list of keyword values
searchLights.options.timing int 90 User settable via the options object. timing sets the inline CSS transition-duration of searchlight elements in milliseconds. When setting up custom pointers, use varying timing values to create different effects.
searchLights.options.ptrEls [array] searchLights.defaults.ptrEls searchLights.options.ptrEls is an array of individual searchlight object element settings used to add searchlight elements to the DOM dynamically. Default values will be added if they are not specifically set. The ptrEls array can hold any searchLights.options value to allow for fine control of each element. Keep in mind that the only "required" ptrEls specific property is color. Without a valid css color value, the searchlight elements will be drawn with transparent pixels, which is likely not what you likely want.
	const srchLts = {
		sParentEl = 'body',
		sTargetClass = '.searchlight',
		bUseInlineStyles = true,
		bUseInlineStyles = true,
	}
	// General settings for all pointers
	srchLts.options = {
		 blur: 3,
        dia: 100,
        blend: 'screen',
        opacity: 0.8,
        easing: 'ease-out',
        timing: 90,
        width: undefined,
        height: undefined,
        color: undefined,
	}
	// Pointer elements with options defined.
   srchLts.options.ptrEls = [
            {
                classes: ['red'],
                color: 'rgb(255,0,0)',
                dia: 150,
                blur: 2,
                blend: 'screen',
                opacity: .8,
                timing: 400,
            },
            {
                classes: ['green'],
                color: 'rgb(0,255,0)',
                dia: 200,
                blur: 4,
                blend: 'difference',
                opacity: .5,
                timing: 325,
            },
            {
                classes: ['blue'],
                color: 'blue',
                dia: 250,
                blur: 3,
                blend: 'exclusion',
                opacity: .4,
                timing: 275,
            },
    ]

    searchLights._Init(srchLts)

Public Methods

Gotchas

possibly using tips here. https://stackoverflow.com/questions/53989222/getting-jank-on-css-transform https://stackoverflow.com/a/54013312/362445

removed position property. used tanslate3d for hardware acceleration. also will-change: transform;

Code Conventions

camelCase All functions and variables should be given descriptive camelCase names using Hungarian Notation.

Hungarian Notation Variables and function names should be prefixed with the following Hungarian notation codes:

Prefix Description
s String
b Boolean
f Float
i Integer
o Object
a Array
fn Function or method
n DOM node
nl DOM nodeList
_ Treat as a private
C Class name prefix

Notable exceptions are searchLights._init() and searchLights._destroy() methods, which are not prefixed with fn, and the serchlights.options and serchlights.optionsobject parameters, which are not prefixed by datatype.

Capitalization: const variables that should not be mutated directly are capitalized. Classe names are prefixed with a capital C

TO DO

  • Make adding pointers to DOM more efficient with an HTML Fragment (we can only do this when we are provided with a target string in _build(); if we are not, then we resort to looking in the DOM elements themselves, to see if we previously saved a srchLtsParentElement. In fnCreateSrchLtEls(), we use the settings or _Default to be more effective with this technique.

  • Revise docs. . . .

  • Make sure we are not directly manipulating incoming params. . . objects & arrays

  • Better handling of the pointer's first appearance on screen (now they sit at 00 until the pointer event.)

  • Consistently sanitize or parse every data-attr before it hits the DOM?

  • Consider a constructor to be able to create more than one.

  • onpointermove, vs onmousemove - onpointermove is janky on FF, but using onmousemove means that pointers aren't registered. Detect if the user is using a pointer.

  • On safari, the drawn borders are not blurred (no support) - fake it with drop shadow?

  • look at dynamically calling methods based on the data-* attr-name. https://www.sitepoint.com/call-javascript-function-string-without-using-eval/

    • This would be useful for regenerating new DOM elements based on existing data-* values. ie
    • given data-opacity='.5' check if a method exists, and if so, run srchlte.opacity(el, .5)
  • Add a flag for the type of element to be made. Global? only or on a per-element basis? hummmmm ie canvas or block....

  • Consider being able to disable inline classes on a per-element basis.

  • Make sure that when adding elements to the DOM, it is done in such a way as to minimise redraw/reflow

These might be best as an experiment in extending the plugin:

  • Automatically calculate degrees of separation for n searchlights
  • Automatically calculate off-centre separation of searchlights provided a distance value
  • Optionally be able to converge searchlights to 0 (cursor tip) after the cursor pauses

Completed

  • Make adding styles more efficient with classes where possible. (I added a hidden attribute; not much else makes sense.
  • add z-index to list of params (in progress)
  • Believe that the ctx element is not getting all the default values. line 441
  • Bug with timing, being passed through as string, should be an int. (we turn it into a string anyway tho...)
  • Hungarian notation (in progress) - http://cws.cengage.co.uk/rautenbach/students/ancillary_content/hungarian_notation.pdf
  • BUG _fnBuildOptionsObj() method works, but it does not add default els to DOM. ln 200
  • FOUC - modify base styles to set to display none, visibility hidden, add a _funSetVisibleDisplay() method to make the el visible on load. Bonus points for using debounce to have _fnSetOpacity run after all the other styles are added for a fade-in effect.
  • _fnBuildOptionsObj method should capture the existing DOM nodes and store them for later.
  • _build() method should take a list of nodes and attach them to the provided target element.
  • Should _fnAssembleSrchLtEls begin with adding the style element to the body? (prob not)
  • _oSrchLtsParentNode rename to _nSrchLtsParentNode
  • Bug - calling init({}) on searchLights when there were already elements in the DOM does not redraw the default ones.... what is happening here?
  • Consider removing the escape hatch if DOM elements exist. This could prevent us from creating algorithmic configurations in the future.
  • Assess if, rather than tracking, the mouse leaves the body to show/hide, instead if it leaves the 'attach' target with the custom event. / changes made to srchLts.m.fnEventSetup
  • Ensure that any provided options are dynamically turned into data-* attributes
  • If we are inlining styles on the element, we should create the basic stylesheet on the fly.
  • destroy method for cleanup . . .
  • Be able to specify in options what element the pointers should be prepended to // done with the 'attach' option
  • Consider referencing the actual ptr DOM element computed width and height rather than trying to calculate centre settings.
  • This will keep the pointer centred even if the values are changed with js later.
  • refactor m.fnCreateSrchLtEls() to allow numeric 0 values from data-*
  • refactor the merging of Defaults and options into the settings object.

@wont Detect when the pointer is over an array of DOM elements and optionally hide the element. @wont convert nodeList.forEach() to Array.prototype.slice.call() for better backward compatibility if we were to use this with (for example) just CSS or perhaps SVG approaches. (not needed according to CF) @wont Consider ways to adjust layering order. Is z-index easiest? // achievable with a callback or event listener. @wont Optionally be able to shuffle or randomize the css transition value for each searchlight after cursor pause to make its behaviour less predictable // could be done with css and external js -->https://css-tricks.com/newsletter/236-initialisms-and-layout-shifts/ && https://imagineer.in/blog/stacking-context-with-opacity/ @wont ::before ::after pseudo-elements in order to transition blending mode on cursor stop? // could be achieved with CSS, so I added a flag to disable inline styles.

About

A fun little project to experiment with mix-blend-mode and drawing with CanvasRenderingContext2D.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published