ViewportFrame Camera/Model ZoomIn/Rotate Hover Effect

Hello, I’ve been recently making a Grow A Garden kit and running into some issues with the ‘hover’ effect on the models/seed shop. I basically want it to zoom in on the model and rotate it almost like a pop up, jiggly effect.

I have a StarterPlayerScript that handles all Tags, so I put the Hover tag like so:


--[[ HOVER EFFECT ]]--

local TweenService = game:GetService("TweenService")

local function applyHoverEffect(button)
	local seedImage = button:WaitForChild("SeedImage")
	local viewportFrame = seedImage:WaitForChild("ViewportFrame")
	local cam = viewportFrame:FindFirstChildWhichIsA("Camera")
	local model = viewportFrame:FindFirstChildWhichIsA("Model")

	if not cam or not model then
		warn("⚠️ Missing camera or model in:", button.Name)
		return
	end

	local originalCF = cam.CFrame

	button.MouseEnter:Connect(function()
		
		print('hover')
		
		local center = model:GetPivot().Position
		local size = model:GetExtentsSize().Magnitude
		local zoomDistance = size / 2 -- closer than original

		-- Add a little bounce pop rotation
		local hoverCF = CFrame.new(center + Vector3.new(0, 0, zoomDistance), center) * CFrame.Angles(math.rad(5), math.rad(2), 0)

		-- Tween a CFrameValue instead
		local cframeValue = Instance.new("CFrameValue")
		cframeValue.Value = cam.CFrame

		-- Apply each frame to the real camera
		local connection = cframeValue:GetPropertyChangedSignal("Value"):Connect(function()
			cam.CFrame = cframeValue.Value
		end)

		-- Animate in
		local tweenIn = TweenService:Create(cframeValue, TweenInfo.new(0.15, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {
			Value = hoverCF
		})
		tweenIn:Play()
		tweenIn.Completed:Connect(function()
			connection:Disconnect()
			cframeValue:Destroy()
		end)
	end)

	button.MouseLeave:Connect(function()
		-- Bounce back
		local cframeValue = Instance.new("CFrameValue")
		cframeValue.Value = cam.CFrame

		local connection = cframeValue:GetPropertyChangedSignal("Value"):Connect(function()
			cam.CFrame = cframeValue.Value
		end)

		local tweenOut = TweenService:Create(cframeValue, TweenInfo.new(0.15, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {
			Value = originalCF
		})
		tweenOut:Play()
		tweenOut.Completed:Connect(function()
			connection:Disconnect()
			cframeValue:Destroy()
		end)
	end)
end

-- Attach hover effect to existing buttons
for _, object in ipairs(playerGui:GetDescendants()) do
	if (object:IsA("TextButton") or object:IsA("ImageButton")) and CollectionService:HasTag(object, "Hover") then
		applyHoverEffect(object)
	end
end

-- Also listen for new hover-tagged buttons at runtime
CollectionService:GetInstanceAddedSignal("Hover"):Connect(function(object)
	if (object:IsA("TextButton") or object:IsA("ImageButton")) and object:IsDescendantOf(playerGui) then
		applyHoverEffect(object)
	end
end)

The way the frames work:



I basically put the model from replicated into ViewPort, then I just set the CFrame in the MainShop script like:

if model then
		local modelClone = model:Clone()
		modelClone.Parent = viewport

		-- ✅ Setup unique camera per crop
		local cam = Instance.new("Camera")
		cam.Name = "ViewportCamera"
		cam.Parent = viewport
		viewport.CurrentCamera = cam

		-- ✅ Ensure model has PrimaryPart
		local primaryPart = modelClone:FindFirstChild("PrimaryPart")
		if primaryPart then
			local center = modelClone.PrimaryPart.Position
			local size = modelClone:GetExtentsSize().Magnitude
			local zoomDistance = size / 2 + 1.5

			cam.CFrame = CFrame.new(center + Vector3.new(0, 0, zoomDistance), center)
		else
			warn("❌ Missing PrimaryPart for", data.Name)
		end
	end

My issue: I’ve literally tried multiple methods, using PrimaryPart, using CFrame for the Camera, or just the Frame itself, and none of them are working. It’s almost like the camera/viewport is just frozen and stuck in time (the part), it won’t move.

Any ideas, on how I can go about this?

Every frame a Camera object must be created or else it wont work. When a player views the seed you could do something like this

--make sure this is on the client ofcourse
local RunService = game:GetService('RunService')

local CamLoop = RunService.RenderStepped:Connect(function()
 local Camera = Instance.new('Camera')
 Camera.Parent = ViewportFrame

 -- ... Camera code

 ViewportFrame.CurrentCamera = Camera
end)
 -- ... Other code

 --Player leaves viewing
CamLoop:Disconnect()
1 Like

Fixed with this, thank you!

local function applyHoverEffect(button)
	local seedImage = button:WaitForChild("SeedImage")
	local viewportFrame = seedImage:WaitForChild("ViewportFrame")
	local model = viewportFrame:FindFirstChildWhichIsA("Model")

	if not model or not model:FindFirstChild("PrimaryPart") then
		warn("⚠️ Missing model or PrimaryPart in:", button.Name)
		return
	end

	local originalCamera = viewportFrame:FindFirstChild("ViewportCamera")
	local originalCFrame = originalCamera and originalCamera.CFrame

	local primaryPart = model.PrimaryPart
	local baseCenter = primaryPart.Position
	local baseSize = model:GetExtentsSize().Magnitude
	local baseZoom = baseSize / 2 + 1
	local closerZoom = baseZoom + 0.10

	local bounceTimer = 0
	local connection

	button.MouseEnter:Connect(function()

		connection = RunService.RenderStepped:Connect(function(dt)
			bounceTimer += dt * 6
			
			local offsetZ = math.sin(bounceTimer) * 0.1 -- bounce effect
			
			-- NEW: Jiggle rotation offsets
			local angleX = math.sin(bounceTimer * 1.2) * math.rad(1.5) -- up/down wobble
			local angleY = math.cos(bounceTimer * 1.5) * math.rad(1.5) -- left/right wobble
		
			local camPos = baseCenter + Vector3.new(0, 0, closerZoom + offsetZ)
			local lookVector = (baseCenter - camPos).Unit

			-- Apply rotation using CFrame.Angles
			local jiggleRotation = CFrame.Angles(angleX, angleY, 0)

			-- Create and assign new camera
			local cam = Instance.new("Camera")
			cam.Name = "ViewportCamera"
			cam.CFrame = CFrame.new(camPos, baseCenter) * jiggleRotation
			cam.Parent = viewportFrame
			viewportFrame.CurrentCamera = cam

			-- Destroy all previous cameras
			for _, obj in ipairs(viewportFrame:GetChildren()) do
				if obj:IsA("Camera") and obj ~= cam then
					obj:Destroy()
				end
			end
		end)
	end)

	button.MouseLeave:Connect(function()
		if connection then
			connection:Disconnect()
			connection = nil
		end

		-- Restore original camera (or fallback to safe position)
		local cam = Instance.new("Camera")
		cam.Name = "ViewportCamera"
		cam.CFrame = originalCFrame or CFrame.new(baseCenter + Vector3.new(0, 0, baseZoom), baseCenter)
		cam.Parent = viewportFrame
		viewportFrame.CurrentCamera = cam

		-- Cleanup
		for _, obj in ipairs(viewportFrame:GetChildren()) do
			if obj:IsA("Camera") and obj ~= cam then
				obj:Destroy()
			end
		end
	end)
end

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.