HTML
CSS
JS
Result
Skip Results Iframe
EDIT ON
Live
Live
Live
This Pen is owned by
web.dev
on
CodePen
.
See more by @web-dot-dev on CodePen
External CSS
https://codepen.io/web-dot-dev/pen/XWqWYgB.css
https://codepen.io/web-dot-dev/pen/ZExZWBQ.css
External JavaScript
https://codepen.io/web-dot-dev/pen/XWqWYgB.js
https://codepen.io/web-dot-dev/pen/ZExZWBQ.js
{"__browser":{"country":"US","device":"unknown_device","mobile":false,"name":"unknown browser","platform":"unknown_platform","version":"0"},"__constants":{},"__CPDATA":{"domain_iframe":"https://cdpn.io","environment":"production","host":"codepen.io","iframe_allow":"accelerometer *; ambient-light-sensor *; camera *; display-capture *; encrypted-media *; geolocation *; gyroscope *; microphone *; midi *; payment *; vr *; web-share *; serial *; xr-spatial-tracking *","iframe_sandbox":"allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups-to-escape-sandbox allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation"},"__graphql":{"data":{"errors":[{"message":"Cannot return null for non-nullable field Query.sessionUser"}],"data":null},"url":"https://codepen.io/graphql","api":"cprails"},"__pay_stripe_public_key":"pk_live_2GndomDfiklqpSNQn8FrGuwZSMIMzha7DkLJqlYe7IR0ihKAlKdiHg68JJc5eVPt68rzAjzAAVXcUwjySHRCsgjQQ00gtRBUFNH","__pay_braintree_env":"production","__boomboom":{"serve_url":"https://cdpn.io/cpe/boomboom","store_url":"https://codepen.io/cpe/boomboom/store"},"__pageType":"embed","__item":"{\"editor_settings\":{\"auto_run\":true,\"autocomplete\":false,\"code_folding\":true,\"css_pre_processor\":\"none\",\"css_prefix\":\"neither\",\"css_starter\":\"neither\",\"emmet_active\":true,\"font_size\":14,\"font_type\":\"system\",\"format_on_save\":true,\"html_pre_processor\":\"none\",\"indent_with\":\"spaces\",\"js_pre_processor\":\"none\",\"key_bindings\":\"normal\",\"line_numbers\":true,\"line_wrapping\":true,\"match_brackets\":true,\"snippets\":{\"markupSnippets\":{},\"stylesheetSnippets\":{}},\"tab_size\":2,\"theme\":\"twilight\",\"id\":\"poVvzer\",\"auto_save\":true},\"hashid\":\"poVvzer\",\"itemType\":\"pen\",\"resources\":[{\"url\":\"https://codepen.io/web-dot-dev/pen/XWqWYgB.css\",\"order\":0,\"resource_type\":\"css\",\"par_content\":\"\"},{\"url\":\"https://codepen.io/web-dot-dev/pen/XWqWYgB.js\",\"order\":0,\"resource_type\":\"js\",\"par_content\":\"\"},{\"url\":\"https://codepen.io/web-dot-dev/pen/ZExZWBQ.css\",\"order\":1,\"resource_type\":\"css\",\"par_content\":\"\"},{\"url\":\"https://codepen.io/web-dot-dev/pen/ZExZWBQ.js\",\"order\":1,\"resource_type\":\"js\",\"par_content\":\"\"}],\"tags\":[],\"id\":62926064,\"user_id\":5928893,\"html\":\"<main>\\n <header>\\n <h1>Custom Cursor Pop-up</h1>\\n </header>\\n <article>\\n <p>\\n Here, a <code>popup</code> displays a custom cursor. Using the top layer, we can put the cursor above everything else. If we need to display another pop-up, we can listen for the event and hide and reshow the cursor popup. Try showing a pop-up above it.\\n </p>\\n <div>\\n <button class=\\\"button ripple\\\" popovertoggletarget=\\\"overlay-pop-up\\\">Show Pop-up</button>\\n </div>\\n </article>\\n</main>\\n<div id=\\\"overlay-pop-up\\\" popover>\\n <div class=\\\"card elevated\\\">\\n You can still see the cursor above me and my <code>::backdrop</code>!\\n </div>\\n</div>\\n<canvas id=\\\"custom-cursor\\\" class=\\\"custom-cursor\\\" popover=\\\"manual\\\"></canvas>\",\"css\":\"@layer demo {\\n #overlay-pop-up {\\n border: 0;\\n background: none;\\n max-width: calc(100% - (2 * var(--size-4)));\\n min-width: 0;\\n }\\n\\n #overlay-pop-up::backdrop {\\n background: hsl(0 0% 0% / 0.25);\\n backdrop-filter: blur(4px);\\n }\\n\\n #overlay-pop-up .card {\\n padding: var(--size-4);\\n display: block;\\n min-width: 0;\\n line-height: 1.5;\\n background: var(--surface-1);\\n color: var(--text-1);\\n }\\n}\\n\\n@layer base {\\n *,\\n *:after,\\n *:before {\\n box-sizing: border-box;\\n /*cursor: none;*/\\n }\\n\\n body {\\n min-height: 100vh;\\n background: var(--surface-1);\\n display: block;\\n font-family: \\\"Google Sans\\\", sans-serif, system-ui;\\n }\\n\\n :where([popover]) {\\n margin: auto;\\n border-width: 0;\\n border-style: none;\\n background: transparent;\\n }\\n\\n canvas {\\n height: 100vh;\\n width: 100vw;\\n pointer-events: none;\\n display: none;\\n position: fixed;\\n }\\n\\n canvas:open {\\n display: block;\\n }\\n\\n h1 {\\n margin: 0;\\n color: var(--gray-0);\\n }\\n\\n .button {\\n /*cursor: none;*/\\n }\\n\\n header {\\n height: 25vmin;\\n min-height: 200px;\\n background: var(--gradient-23);\\n display: grid;\\n place-items: center;\\n color: var(--gray-0);\\n padding: var(--size-4);\\n grid-template-columns: 1fr;\\n }\\n\\n main {\\n margin: 0 auto;\\n }\\n\\n article {\\n padding: var(--size-4);\\n display: flex;\\n flex-direction: column;\\n align-items: center;\\n }\\n\\n article > * + * {\\n margin-top: var(--size-4);\\n }\\n}\\n\",\"js\":\"const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => {\\n const INPUT_RANGE = inputUpper - inputLower;\\n const OUTPUT_RANGE = outputUpper - outputLower;\\n return (value) =>\\n outputLower + (((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0);\\n};\\n\\nconst clamp = (min, max, value) => Math.min(Math.max(value, min), max);\\n\\nlet canvas;\\nlet context;\\nconst blocks = [];\\n\\nconst createBlock = ({ x, y, movementX, movementY }) => {\\n const inactiveBlocks = blocks.filter((block) => block.active === false);\\n const size = Math.floor(\\n mapRange(0, 100, 10, 50)(Math.max(Math.abs(movementX), Math.abs(movementY)))\\n );\\n if (inactiveBlocks.length !== 0) {\\n // Reuse a block\\n inactiveBlocks[0].active = true;\\n inactiveBlocks[0].size = size;\\n inactiveBlocks[0].x = x - size * 0.5;\\n inactiveBlocks[0].y = y - size * 0.5;\\n inactiveBlocks[0].hue = Math.floor(Math.random() * 359);\\n } else {\\n const block = {\\n active: true,\\n hue: Math.floor(Math.random() * 359),\\n x: x - size * 0.5,\\n y: y - size * 0.5,\\n size\\n };\\n blocks.push(block);\\n }\\n};\\n\\nconst drawBlocks = () => {\\n context.clearRect(0, 0, canvas.width, canvas.height);\\n for (const block of blocks.filter((block) => block.active)) {\\n context.strokeStyle = context.fillStyle = `hsl(${block.hue}, 80%, 80%)`;\\n context.beginPath();\\n context.arc(block.x, block.y, block.size * 0.5, 0, 2 * Math.PI);\\n context.stroke();\\n context.fill();\\n\\n block.size -= 1;\\n if (block.size === 0) {\\n block.active = false;\\n }\\n }\\n requestAnimationFrame(drawBlocks);\\n};\\n\\nconst setUp = () => {\\n canvas = document.querySelector(\\\"canvas\\\");\\n context = canvas.getContext(\\\"2d\\\");\\n canvas.width = window.innerWidth;\\n canvas.height = window.innerHeight;\\n document.body.addEventListener(\\\"pointermove\\\", createBlock);\\n // Listen for any pop-ups that are opened and make sure the canvas sits higher\\n document.body.addEventListener(\\\"beforetoggle\\\", (e) => {\\n if (e.newState === \\\"open\\\" && e.target !== canvas) {\\n requestAnimationFrame(() => {\\n canvas.hidePopover();\\n canvas.showPopover();\\n });\\n }\\n });\\n requestAnimationFrame(drawBlocks);\\n};\\n\\nsetUp();\\nwindow.addEventListener(\\\"resize\\\", () => {\\n if (canvas) {\\n canvas.width = window.innerWidth;\\n canvas.height = window.innerHeight;\\n }\\n});\\n\\ncanvas.showPopover();\\n\",\"html_pre_processor\":\"none\",\"css_pre_processor\":\"none\",\"js_pre_processor\":\"none\",\"html_classes\":\"popup-support\",\"css_starter\":\"neither\",\"js_library\":null,\"created_at\":\"2022-09-03T20:11:12.014Z\",\"updated_at\":\"2023-01-19T11:21:35.793Z\",\"title\":\"20. Custom cursor pop-up\",\"description\":\"\",\"slug_hash\":\"poVvzer\",\"head\":\"\",\"private\":false,\"has_animation\":true,\"team_id\":0,\"css_prefix\":\"neither\",\"template\":false,\"parent_id\":0,\"comments_count\":0,\"custom_screenshot_filename\":null,\"loves_count\":0,\"pick\":false,\"popularity_score\":0,\"views_count\":0,\"pick_visible_at\":null,\"cpid\":\"018304fa-8b0e-7a3b-8e6c-b544822668a7\",\"is_new_editor_pen\":false,\"protected\":false,\"access\":\"Public\",\"pen_hash\":null}","__processorsMap":{"autoprefixer":"autoprefixer-10","babel":"babel-7","coffeescript":"coffeescript-2","format-1":"format-1","flutter":"flutter-1","haml":"haml-4","less":"less-3","lint-1":"lint-1","livescript":"livescript-1","markdown":"markdown-11","postcss":"postcss-7","pug":"pug-2","sass":"sass-1","scss":"sass-1","sass-ruby-3":"sass-ruby-3","sass-ruby-compass-3":"sass-ruby-compass-3","slim":"slim-3","stylus":"stylus-0","typescript":"typescript-4","vue":"vue-2","vue3":"vue-3"},"__favicon_mask_icon":"https://cpwebassets.codepen.io/assets/favicon/logo-pin-b4b4269c16397ad2f0f7a01bcdf513a1994f4c94b8af2f191c09eb0d601762b1.svg","__favicon_shortcut_icon":"https://cpwebassets.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico","__path_to_iframe_console_runner":"https://cpwebassets.codepen.io/assets/editor/iframe/iframeConsoleRunner-6d8bf8b4b479137260842506acbb12717dace0823c023e08b96360e60b0840d9.js","__path_to_iframe_refresh_css":"https://cpwebassets.codepen.io/assets/editor/iframe/iframeRefreshCSS-44fe83e49b63affec96918c9af88c0d80b209a862cf87ac46bc933074b8c557d.js","__path_to_iframe_runtime_errors":"https://cpwebassets.codepen.io/assets/editor/iframe/iframeRuntimeErrors-4f205f2c14e769b448bcf477de2938c681660d5038bc464e3700256713ebe261.js","__path_to_processor_worker":"https://cpwebassets.codepen.io/assets/packs/router.js","__path_to_stop_execution_on_timeout":"https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-2c7831bb44f98c1391d6a4ffda0e1fd302503391ca806e7fcc7b9b87197aec26.js","__pen_normalize_css_url":"https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css","__pen_prefix_free_url":"https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js","__pen_reset_css_url":"https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css","__path_to_infinite_loop_detection":"https://cpwebassets.codepen.io/assets/packs/js/infiniteLoopDetection-2da1288227e223513db7.js"}