├── .github ├── DISCUSSION_TEMPLATE │ ├── ideas.yml │ └── q-a.yml ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── dependabot.yml └── workflows │ └── release.yml ├── .gitignore ├── CITATION.cff ├── LICENSE ├── README.md ├── _extensions └── spotlight │ ├── LICENSE │ ├── _extension.yml │ └── spotlight.js └── example.qmd /.github/DISCUSSION_TEMPLATE/ideas.yml: -------------------------------------------------------------------------------- 1 | title: "The (missing) feature you want to discuss" 2 | labels: 3 | - "Type: Enhancement :bulb:" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | First, please check if your feature request has already been discussed. 9 | If it has, please consider adding a comment to the existing discussion instead of creating a new one. 10 | 11 | If you want to ask for help, please consider using the Q&A GitHub Discussions, as it is better suited for answering your question. 12 | 13 | Finally, try to describe the best you can what you want to achieve and why you think it is important. 14 | This will help us to understand your request and prioritise it. 15 | 16 | _Thank you for opening this feature request!_ 17 | - type: textarea 18 | attributes: 19 | label: Description 20 | description: Start your discussion! 21 | validations: 22 | required: true 23 | - type: markdown 24 | attributes: 25 | value: | 26 | _Thank you for opening this feature request!_ 27 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/q-a.yml: -------------------------------------------------------------------------------- 1 | title: "Your question in one sentence" 2 | body: 3 | - type: markdown 4 | attributes: 5 | value: | 6 | First, please check if your question has already been asked. 7 | If it has, please consider adding a comment to the existing discussion instead of creating a new one. 8 | 9 | Finally, try to describe the best you can what you want to achieve or what is your issue, especially by providing a complete self-contained reproducible example. 10 | This will help us to understand your question and answer it. 11 | 12 | `````md 13 | ````qmd 14 | --- 15 | title: "Reproducible Quarto Document" 16 | format: html 17 | engine: jupyter 18 | --- 19 | 20 | This is a reproducible Quarto document using `format: html`. 21 | It is written in Markdown and contains embedded Python code. 22 | When you run the code, it will produce a message. 23 | 24 | ```{python} 25 | print("Hello, world!") 26 | ``` 27 | 28 | ![An image]({{< placeholder 600 400 >}}){#fig-placeholder} 29 | 30 | {{< lipsum 1 >}} 31 | 32 | A reference to @fig-placeholder. 33 | 34 | The end. 35 | ```` 36 | ````` 37 | 38 | You can add some additional information that can help us to help you: 39 | 40 | - What Quarto version are you using? 41 | You can find the version of Quarto you used by running `quarto --version` in your terminal. 42 | - What version of the Quarto extension are you using? 43 | You can find the version of the Quarto extension you used by running `quarto list extensions` in your terminal. 44 | - What operating system are you using? 45 | Linux Ubuntu 20.04, macOS 11.2.3, Windows 10, _etc._ 46 | 47 | _Thank you for opening this discussion either to ask for help or to report a possible bug!_ 48 | - type: textarea 49 | attributes: 50 | label: Description 51 | description: Start your question/report! 52 | placeholder: | 53 | You can include Quarto document code which includes code blocks like this: 54 | 55 | `````md 56 | ````qmd 57 | --- 58 | title: "Reproducible Quarto Document" 59 | format: html 60 | engine: jupyter 61 | --- 62 | 63 | This is a reproducible Quarto document using `format: html`. 64 | It is written in Markdown and contains embedded Python code. 65 | When you run the code, it will produce a message. 66 | 67 | ```{python} 68 | print("Hello, world!") 69 | ``` 70 | 71 | ![An image]({{< placeholder 600 400 >}}){#fig-placeholder} 72 | 73 | {{< lipsum 1 >}} 74 | 75 | A reference to @fig-placeholder. 76 | 77 | The end. 78 | ```` 79 | ````` 80 | 81 | --- 82 | 83 | ### Additional information that can help us to help you 84 | 85 | - What Quarto version are you using? 86 | You can find the version of Quarto you used by running `quarto --version` in your terminal. 87 | - What version of the Quarto extension are you using? 88 | You can find the version of the Quarto extension you used by running `quarto list extensions` in your terminal. 89 | - What operating system are you using? 90 | Linux Ubuntu 20.04, macOS 11.2.3, Windows 10, _etc._ 91 | validations: 92 | required: true 93 | - type: markdown 94 | attributes: 95 | value: | 96 | _Thank you for opening this discussion either to ask for help or to report a possible bug!_ 97 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mcanouil 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report an error or unexpected behaviour 3 | labels: 4 | - "Type: Bug :bug:" 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Welcome! 11 | 12 | - type: checkboxes 13 | attributes: 14 | label: "I Have checked the following" 15 | options: 16 | - label: I have searched the issue tracker for similar issues 17 | required: true 18 | - label: I have the latest version of [Quarto CLI](https://github.com/quarto-dev/quarto-cli/releases/latest) 19 | required: true 20 | - label: I have the latest version of the Quarto extension 21 | required: true 22 | 23 | - type: textarea 24 | attributes: 25 | label: Bug description 26 | description: Description of the bug. 27 | placeholder: Please describe the bug here. 28 | 29 | - type: textarea 30 | attributes: 31 | label: Steps to reproduce 32 | description: | 33 | Tell us how to reproduce this bug. 34 | 35 | - type: textarea 36 | attributes: 37 | label: Actual behaviour 38 | description: Tell us what happens instead. 39 | 40 | - type: textarea 41 | attributes: 42 | label: Expected behaviour 43 | description: Tell us what should happen. 44 | 45 | - type: textarea 46 | attributes: 47 | label: Your environment 48 | description: | 49 | Please document the IDE (_e.g._ RStudio, Positron, VSCode, NVim), its version, and the operating system you're running (_e.g., MacOS Ventura 13.4, Windows 11, Linux Debian 11, _etc._). 50 | placeholder: | 51 | - IDE: RStudio 2023.03.1+446 | VSCode | Positron 52 | - OS: MacOS Ventura 13.4 | Windows | Ubuntu 53 | 54 | - type: markdown 55 | attributes: 56 | value: "_Thanks for submitting this bug report!_" 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Support 4 | url: https://github.com/mcanouil/quarto-spotlight/discussions 5 | about: Please ask and answer questions here. 6 | - name: Issue with Quarto CLI 7 | url: https://github.com/quarto-dev/quarto-cli 8 | about: Please report issues with the Quarto CLI here 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | labels: 8 | - "Type: Dependencies :arrow_up:" 9 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Quarto Extension 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | type: choice 8 | description: "Version" 9 | required: false 10 | default: "patch" 11 | options: 12 | - "patch" 13 | - "minor" 14 | - "major" 15 | quarto: 16 | type: choice 17 | description: "Quarto version" 18 | required: false 19 | default: "release" 20 | options: 21 | - "release" 22 | - "pre-release" 23 | 24 | permissions: 25 | contents: write 26 | pull-requests: write 27 | id-token: write 28 | pages: write 29 | 30 | jobs: 31 | release: 32 | uses: mcanouil/quarto-workflows/.github/workflows/release-extension.yml@main 33 | secrets: inherit 34 | with: 35 | version: "${{ github.event.inputs.version }}" 36 | formats: "revealjs" 37 | tinytex: false 38 | quarto: "${{ github.event.inputs.quarto }}" 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *_files/ -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: "Spotlight Extension for Quarto" 3 | message: "If you use this project, please cite it as below." 4 | type: software 5 | authors: 6 | - family-names: "Canouil" 7 | given-names: "Mickaël" 8 | orcid: "https://orcid.org/0000-0002-3396-4549" 9 | repository-code: "https://github.com/mcanouil/quarto-spotlight" 10 | url: "http://m.canouil.dev/quarto-spotlight/" 11 | license: "MIT" 12 | date-released: "2025-04-05" 13 | version: 1.0.2 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mickaël Canouil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotlight Extension For Quarto `revealjs` Format 2 | 3 | A plugin for [Reveal.js](https://github.com/hakimel/reveal.js) allowing to highlight the current mouse position with a spotlight. 4 | 5 | ## Installing 6 | 7 | ```bash 8 | quarto add mcanouil/quarto-spotlight 9 | ``` 10 | 11 | This will install the extension under the `_extensions` subdirectory. 12 | If you're using version control, you will want to check in this directory. 13 | 14 | ## Usage 15 | 16 | Simply add the extension to the list of reveal plugins like: 17 | 18 | ```yaml 19 | format: 20 | revealjs: default 21 | spotlight: 22 | # set pointer configuration options here 23 | revealjs-plugins: 24 | - spotlight 25 | ``` 26 | 27 | ## Options 28 | 29 | You can control the appearance of the pointer by passing some additional options under a `spotlight` key. 30 | 31 | | Option | Description | 32 | | ---------------------------- | ----------------------------------------------------------------------------------------------------- | 33 | | `size` | size of the spotlight. Default is `60`. | 34 | | `lockPointerInsideCanvas` | Locks the mouse pointer inside the presentation. Default is `false`. | 35 | | `toggleSpotlightOnMouseDown` | Toggle spotlight by holding down the mouse key. Default is `true`. | 36 | | `spotlightOnKeyPressAndHold` | The key code pressed and held to turn on spotlight, disabled when set to false. Default is `false`. | 37 | | `spotlightCursor` | The cursor when spotlight is on. Can be "crosshair". Default is `none`. | 38 | | `presentingCursor` | The cursor when spotlight is off and in presentation mode. Can be "default". Default is `none`. | 39 | | `initialPresentationMode` | Enable presentation mode, will also be true if toggleSpotlightOnMouseDown is true. Default is `true`. | 40 | | `disablingUserSelect` | Disable selecting in presentation mode. Default is `true`. | 41 | | `fadeInAndOut` | Transition duration in ms to enable fade in and out, disabled when set to false. Default is `100`. | 42 | | `useAsPointer` | Enable pointer mode. Default is `false`. | 43 | | `pointerColor` | Set pointer colour if pointer mode is enabled. Default is `red`. | 44 | 45 | ## Example 46 | 47 | Here is the source code for a minimal example: [example.qmd](example.qmd). 48 | View an example presentation at . 49 | 50 | --- 51 | 52 | [Spotlight - Reveal.js Plugin](https://github.com/denniskniep/reveal.js-plugin-spotlight) by Dennis Kniep ([@denniskniep](https://github.com/denniskniep)) under Apache License 2.0. 53 | -------------------------------------------------------------------------------- /_extensions/spotlight/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mickaël Canouil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_extensions/spotlight/_extension.yml: -------------------------------------------------------------------------------- 1 | title: Spotlight Reveal.js Plugin 2 | author: Mickaël Canouil 3 | version: 1.0.2 4 | quarto-required: ">=1.2.280" 5 | contributes: 6 | revealjs-plugins: 7 | - name: RevealSpotlight 8 | script: spotlight.js 9 | config: 10 | spotlight: 11 | # size of the spotlight 12 | size: 60 13 | # true: Locks the mouse pointer inside the presentation 14 | # there is by design (Pointer Lock API) no spotlightCursor and presentingCursor 15 | # displayed regardless of the configured values 16 | lockPointerInsideCanvas: false 17 | # toggle spotlight by holding down the mouse key 18 | toggleSpotlightOnMouseDown: true 19 | # the keyCode pressed and held to turn on spotlight, disabled when set to false 20 | # Problems with this config? Maybe your touchpad is disabled on keypress? 21 | spotlightOnKeyPressAndHold: false 22 | # choose the cursor when spotlight is on. Maybe "crosshair"? 23 | spotlightCursor: "none" 24 | # choose the cursor when spotlight is off and in presentation mode. Maybe "default"? 25 | presentingCursor: "none" 26 | # true: initially in presentation mode, will also be ture if this is not set and toggleSpotlightOnMouseDown is true 27 | initialPresentationMode: true 28 | # true: disable selecting in presentation mode 29 | disablingUserSelect: true 30 | # set to a number as transition duration in ms to enable fade in and out, disabled when set to false 31 | fadeInAndOut: 100 32 | # enable pointer mode 33 | useAsPointer: false 34 | # pointer color (If pointer mode enabled) 35 | pointerColor: "red" 36 | -------------------------------------------------------------------------------- /_extensions/spotlight/spotlight.js: -------------------------------------------------------------------------------- 1 | // Source: https://github.com/denniskniep/reveal.js-plugin-spotlight 2 | // Author: Dennis Kniep 3 | // License: Apache License 2.0 4 | 5 | var RevealSpotlight = window.RevealSpotlight || (function () { 6 | 7 | //configs 8 | var spotlightSize; 9 | var toggleOnMouseDown; 10 | var spotlightOnKeyPressAndHold; 11 | var presentingCursor; 12 | var spotlightCursor; 13 | var initialPresentationMode; 14 | var disablingUserSelect; 15 | var fadeInAndOut; 16 | var style; 17 | var lockPointerInsideCanvas; 18 | var getMousePos; 19 | 20 | var drawBoard; 21 | var isSpotlightOn = true; 22 | var isCursorOn = true; 23 | 24 | var lastMouseMoveEvent; 25 | 26 | function onRevealJsReady(event) { 27 | configure(); 28 | drawBoard = setupCanvas(); 29 | 30 | addWindowResizeListener(); 31 | 32 | addMouseMoveListener(); 33 | 34 | if (toggleOnMouseDown) { 35 | addMouseToggleSpotlightListener(); 36 | } 37 | 38 | if (spotlightOnKeyPressAndHold) { 39 | addKeyPressAndHoldSpotlightListener(spotlightOnKeyPressAndHold); 40 | } 41 | 42 | setSpotlight(false); 43 | setCursor(!initialPresentationMode); 44 | } 45 | 46 | function configure() { 47 | var config = Reveal.getConfig().spotlight || {}; 48 | spotlightSize = config.size || 60; 49 | presentingCursor = config.presentingCursor || "none"; 50 | spotlightCursor = config.spotlightCursor || "none"; 51 | var useAsPointer = config.useAsPointer || false; 52 | var pointerColor = config.pointerColor || 'red'; 53 | lockPointerInsideCanvas = config.lockPointerInsideCanvas || false; 54 | 55 | if(lockPointerInsideCanvas){ 56 | getMousePos = getMousePosByMovement; 57 | } else { 58 | getMousePos = getMousePosByBoundingClientRect; 59 | } 60 | 61 | // If using as pointer draw a transparent background and 62 | // the mouse pointer in the specified color or default 63 | var pointerStyle = { 64 | backgroundFillStyle : "rgba(0, 0, 0, 0)", 65 | mouseFillStyle : pointerColor 66 | }; 67 | 68 | var spotlightStyle = { 69 | backgroundFillStyle : "#000000A8", 70 | mouseFillStyle : "#FFFFFFFF" 71 | }; 72 | 73 | style = useAsPointer ? pointerStyle : spotlightStyle; 74 | 75 | if (config.hasOwnProperty("toggleSpotlightOnMouseDown")) { 76 | toggleOnMouseDown = config.toggleSpotlightOnMouseDown; 77 | } else { 78 | toggleOnMouseDown = true; 79 | } 80 | 81 | if (config.hasOwnProperty("initialPresentationMode")) { 82 | initialPresentationMode = config.initialPresentationMode; 83 | } else { 84 | initialPresentationMode = toggleOnMouseDown; 85 | } 86 | 87 | if (config.hasOwnProperty("spotlightOnKeyPressAndHold")) { 88 | spotlightOnKeyPressAndHold = config.spotlightOnKeyPressAndHold; 89 | } else { 90 | spotlightOnKeyPressAndHold = false; 91 | } 92 | 93 | if (config.hasOwnProperty("disablingUserSelect")) { 94 | disablingUserSelect = config.disablingUserSelect; 95 | } else { 96 | disablingUserSelect = true; 97 | } 98 | 99 | if (config.hasOwnProperty("fadeInAndOut")) { 100 | fadeInAndOut = config.fadeInAndOut; 101 | } else { 102 | fadeInAndOut = false; 103 | } 104 | } 105 | 106 | function setupCanvas() { 107 | var container = document.createElement('div'); 108 | container.id = "spotlight"; 109 | container.style.cssText = "position:absolute;top:0;left:0;bottom:0;right:0;z-index:99;"; 110 | if (fadeInAndOut) { 111 | container.style.cssText += "transition: " + fadeInAndOut + "ms opacity;"; 112 | } 113 | 114 | var canvas = document.createElement('canvas'); 115 | var context = canvas.getContext("2d"); 116 | 117 | canvas.width = window.innerWidth; 118 | canvas.height = window.innerHeight; 119 | 120 | container.appendChild(canvas); 121 | document.body.appendChild(container); 122 | container.style.opacity = 0; 123 | container.style['pointer-events'] = 'none'; 124 | return { 125 | container, 126 | canvas, 127 | context 128 | } 129 | } 130 | 131 | function addWindowResizeListener() { 132 | window.addEventListener('resize', function (e) { 133 | var canvas = drawBoard.canvas; 134 | canvas.width = window.innerWidth; 135 | canvas.height = window.innerHeight; 136 | }, false); 137 | } 138 | 139 | function addMouseMoveListener() { 140 | window.addEventListener('mousemove', function (e) { 141 | if(isSpotlightOn) { 142 | showSpotlight(e); 143 | } 144 | lastMouseMoveEvent = e; 145 | }, false); 146 | } 147 | 148 | function addMouseToggleSpotlightListener() { 149 | 150 | window.addEventListener("mousedown", function (e) { 151 | if (!isCursorOn) { 152 | setSpotlight(true, e); 153 | } 154 | }, false); 155 | 156 | window.addEventListener("mouseup", function (e) { 157 | if (!isCursorOn) { 158 | setSpotlight(false, e); 159 | } 160 | }, false); 161 | } 162 | 163 | function addKeyPressAndHoldSpotlightListener(keyCode) { 164 | 165 | window.addEventListener("keydown", function (e) { 166 | if (!isCursorOn && e.keyCode === keyCode) { 167 | setSpotlight(true, lastMouseMoveEvent); 168 | } 169 | }, false); 170 | 171 | window.addEventListener("keyup", function (e) { 172 | if (!isCursorOn && e.keyCode === keyCode) { 173 | setSpotlight(false); 174 | } 175 | }, false); 176 | } 177 | 178 | function toggleSpotlight() { 179 | setSpotlight(!isSpotlightOn, lastMouseMoveEvent); 180 | } 181 | 182 | function setSpotlight(isOn, mouseEvt) { 183 | isSpotlightOn = isOn; 184 | var container = drawBoard.container; 185 | if (isOn) { 186 | if (lockPointerInsideCanvas && document.pointerLockElement != drawBoard.canvas) { 187 | drawBoard.canvas.requestPointerLock(); 188 | } 189 | container.style.opacity = 1; 190 | container.style['pointer-events'] = null; 191 | document.body.style.cursor = spotlightCursor; 192 | if (mouseEvt) { 193 | showSpotlight(mouseEvt); 194 | } 195 | } else { 196 | container.style.opacity = 0; 197 | container.style['pointer-events'] = 'none'; 198 | document.body.style.cursor = presentingCursor; 199 | } 200 | } 201 | 202 | function togglePresentationMode() { 203 | setCursor(!isCursorOn); 204 | } 205 | 206 | function setCursor(isOn) { 207 | isCursorOn = isOn; 208 | if (isOn) { 209 | setSpotlight(false); 210 | if (disablingUserSelect) { 211 | document.body.style.userSelect = null; 212 | document.body.style.MozUserSelect = null; 213 | } 214 | document.body.style.cursor = null; 215 | if(lockPointerInsideCanvas && document.pointerLockElement === drawBoard.canvas){ 216 | document.exitPointerLock(); 217 | } 218 | } else { 219 | if (disablingUserSelect) { 220 | document.body.style.userSelect = "none"; 221 | document.body.style.MozUserSelect = "none"; 222 | } 223 | if (lockPointerInsideCanvas && document.pointerLockElement != drawBoard.canvas) { 224 | drawBoard.canvas.requestPointerLock(); 225 | } 226 | document.body.style.cursor = presentingCursor; 227 | } 228 | } 229 | 230 | function showSpotlight(mouseEvt) { 231 | var canvas = drawBoard.canvas; 232 | var context = drawBoard.context; 233 | var mousePos = getMousePos(canvas, mouseEvt); 234 | 235 | context.clearRect(0, 0, canvas.width, canvas.height); 236 | 237 | // Create a canvas mask 238 | var maskCanvas = document.createElement('canvas'); 239 | maskCanvas.width = canvas.width; 240 | maskCanvas.height = canvas.height; 241 | 242 | var maskCtx = maskCanvas.getContext('2d'); 243 | 244 | maskCtx.fillStyle = style.backgroundFillStyle; 245 | maskCtx.fillRect(0, 0, maskCanvas.width, maskCanvas.height); 246 | maskCtx.globalCompositeOperation = 'xor'; 247 | 248 | maskCtx.fillStyle = style.mouseFillStyle; 249 | maskCtx.arc(mousePos.x, mousePos.y, spotlightSize, 0, 2 * Math.PI); 250 | maskCtx.fill(); 251 | 252 | context.drawImage(maskCanvas, 0, 0); 253 | } 254 | 255 | var mX = 0; 256 | var mY = 0; 257 | 258 | function getMousePosByMovement(canvas, evt) { 259 | var movementX = evt.movementX || 0; 260 | var movementY = evt.movementY || 0; 261 | mX += movementX; 262 | mY += movementY; 263 | 264 | if (mX > canvas.clientWidth) { 265 | mX = canvas.clientWidth; 266 | } 267 | if (mY > canvas.clientHeight) { 268 | mY = canvas.clientHeight; 269 | } 270 | if (mX < 0) { 271 | mX = 0; 272 | } 273 | if (mY < 0) { 274 | mY = 0; 275 | } 276 | 277 | return { 278 | x: mX, 279 | y: mY 280 | }; 281 | } 282 | 283 | function getMousePosByBoundingClientRect(canvas, evt) { 284 | var rect = canvas.getBoundingClientRect(); 285 | return { 286 | x: evt.clientX - rect.left, 287 | y: evt.clientY - rect.top 288 | }; 289 | } 290 | 291 | Reveal.addEventListener('ready', onRevealJsReady); 292 | 293 | this.toggleSpotlight = toggleSpotlight; 294 | this.togglePresentationMode = togglePresentationMode; 295 | return this; 296 | })(); 297 | -------------------------------------------------------------------------------- /example.qmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Spotlight Example" 3 | format: revealjs 4 | revealjs-plugins: 5 | - spotlight 6 | --- 7 | 8 | # In the morning 9 | 10 | ## Getting up 11 | 12 | - Turn off alarm 13 | - Get out of bed 14 | 15 | ## Breakfast 16 | 17 | - Eat eggs 18 | - Drink coffee 19 | 20 | # In the evening 21 | 22 | ## Dinner 23 | 24 | - Eat spaghetti 25 | - Drink wine 26 | 27 | ## Going to sleep 28 | 29 | - Get in bed 30 | - Count sheep 31 | --------------------------------------------------------------------------------