Hello. I am trying to make a system in a scrolling frame that allows you to pan across the canvas when click is held down, similar to the Minecraft advancement screen.
My thought process is to convert the players original and final mouse position at the beginning and end of inputs to a normalized CanvasPosition/AbsolutePosition vector, and then add/subtract that value from their current canvas position.
Here is my code:
local scrollFrame = script.Parent
local UIS = game:GetService("UserInputService")
local dragging = false
local dragStart = nil
local startCanvas = nil
local mouse = game.Players.LocalPlayer:GetMouse()
local originalMousePosX
local originalMousePosY
-- absolute canvas size -> (screengui.absoluteSizeX * canvasScaleX), (screengui.absoluteSizeY * canvasScaleY)
local function normalize(mouseX, mouseY)
local frameTopLeft = script.Parent.AbsolutePosition
local canvasPosition = script.Parent.CanvasPosition
local canvasSize = script.Parent.AbsoluteCanvasSize
-- Convert mouse to position relative to the canvas
local relativeX = (mouseX - frameTopLeft.X + canvasPosition.X) / canvasSize.X
local relativeY = (mouseY - frameTopLeft.Y + canvasPosition.Y) / canvasSize.Y
return relativeX, relativeY
end
-- start dragging
scrollFrame.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = true
originalMousePosX, originalMousePosY = normalize(mouse.X, mouse.Y)
end
end)
-- stop dragging
scrollFrame.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = false
local newMouseX, newMouseY = normalize(mouse.X, mouse.Y)
local deltaX = newMouseX - originalMousePosX
local deltaY = newMouseY - originalMousePosY
print(deltaX, deltaY)
end
end)
-- drag
scrollFrame.InputChanged:Connect(function(input)
if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
script.Parent.Frame.Position = UDim2.fromScale(normalize(mouse.X, mouse.Y))
end
end)
I’ve successfully been able to drag a frame across the canvas when the player scrolls as a kind of proof of concept, but don’t know where to go from here.
I fixed the issue, heres the full script if anyone wants it
local scrollFrame = script.Parent
local dragging = false
local dragStartMousePos = Vector2.new()
local startCanvasPos = Vector2.new()
-- Top left (Min)
local MinCanvasPosition = UDim2.fromScale(0, 0)
-- Bottom Right (Max)
local MaxCanvasPositionX = scrollFrame.AbsoluteCanvasSize.X - scrollFrame.AbsoluteWindowSize.X
local MaxCanvasPositionY = scrollFrame.AbsoluteCanvasSize.Y - scrollFrame.AbsoluteWindowSize.Y
-- absolute canvas size -> (screengui.absoluteSizeX * canvasScaleX), (screengui.absoluteSizeY * canvasScaleY)
-- goal: go from absolute position in the frame to canvas position
-- Converts the players mouse position into a valid relative gui position
local function normalize(mouseX, mouseY)
local frameTopLeft = script.Parent.AbsolutePosition
local canvasPosition = script.Parent.CanvasPosition
local canvasSize = script.Parent.AbsoluteCanvasSize
-- Convert mouse to position relative to the canvas
local relativeX = (mouseX - frameTopLeft.X + canvasPosition.X) / canvasSize.X
local relativeY = (mouseY - frameTopLeft.Y + canvasPosition.Y) / canvasSize.Y
return relativeX, relativeY
end
-- Converts relative gui position to a position relative to the abs size of the canvas
local function convertScaleToAbsolute(scalePosX, scalePosY)
local canvasSize = scrollFrame.AbsoluteCanvasSize
local absX = scalePosX * canvasSize.X
local absY = scalePosY * canvasSize.Y
return absX, absY
end
-- convert mouse position relative 2 gui size to actual canvas position values
local function absoluteToCanvasPosition(absX, absY)
local canvasSize = scrollFrame.AbsoluteCanvasSize
local windowSize = scrollFrame.AbsoluteSize
-- Clamp the scroll position so it stays inside valid scroll bounds
local maxScrollX = math.max(canvasSize.X - windowSize.X, 0)
local maxScrollY = math.max(canvasSize.Y - windowSize.Y, 0)
-- Clamp absX and absY to [0, maxScroll]
local scrollX = math.clamp(absX, 0, maxScrollX)
local scrollY = math.clamp(absY, 0, maxScrollY)
return Vector2.new(scrollX, scrollY)
end
-- start dragging
scrollFrame.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = true
-- Save mouse position on drag start
dragStartMousePos = Vector2.new(input.Position.X, input.Position.Y)
-- Save current scroll position on drag start
startCanvasPos = scrollFrame.CanvasPosition
end
end)
-- stop dragging
scrollFrame.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
dragging = false
end
end)
-- drag
scrollFrame.InputChanged:Connect(function(input)
if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
local currentMousePos = Vector2.new(input.Position.X, input.Position.Y)
-- Calculate delta movement from drag start
local delta = currentMousePos - dragStartMousePos
-- Subtract delta to scroll opposite direction of mouse drag
local newCanvasPos = startCanvasPos - delta
-- Clamp new scroll position to valid canvas bounds
local canvasSize = scrollFrame.AbsoluteCanvasSize
local windowSize = scrollFrame.AbsoluteSize
local maxScrollX = math.max(canvasSize.X - windowSize.X, 0)
local maxScrollY = math.max(canvasSize.Y - windowSize.Y, 0)
newCanvasPos = Vector2.new(
math.clamp(newCanvasPos.X, 0, maxScrollX),
math.clamp(newCanvasPos.Y, 0, maxScrollY)
)
scrollFrame.CanvasPosition = newCanvasPos
end
end)